Hello world!

Welcome to WordPress.com. This is your first post. Edit or delete it and start blogging!

Posted in Uncategorized | 1 Comment

Parasitas que me chupam!!!

Os parasitas tem sido um incomodo. Tenho trabalhado muito neste Natal e já superei minha marca de horas trabalhadas em um mês. Foram mais de 400 horas de trabalho em menos de 30 dias.
Sem dúvida alguma, algo que tinha previsão de ser feito em três anos, por várias empresas e pessoas colaborando, ser feito no final das contas por uma única pessoa, um único desenvolvedor, e ser feito, em menos de 30 dias, é um grande feito. Para quem?! Deveria ser para mim, mas no que depender de parasitas, não será.
Eu, sozinho e também por intermédio de minha empresa, a Solvoj, tenho prestado diferentes tipos de serviços ligados a GIS, desde 2005. De lá para cá, trabalhei com GPS, rastreadores veiculares, sistemas de rastreamento, mapas, etc, inclusive em 2005, começou a nascer o JBemTeVi que ficou pronto somente em 2006, após um ano inteiro de trabalho dedicado a ele e meu pedido de demissão da WeDo em janeiro de 2006, para que pudesse investir nesse produto.
Ainda, desenvolvi aplicativos e utilitários para GPS e sistemas de rastreamento, com ou sem o JBemTeVi. Implementei visualizadores e componentes para diversos formatos cartográficos.
E eis que tenho trabalhado como um burro de carga enquanto os carrapatos se lambuzam de meu sangue neste sol ardente do meu quarto aqui em Icarái, Niterói-RJ.
Estou a uma quadra da praia e se esticar o pescoço para fora da janela, veria o mar, se não fosse uma árvore que tem a frente. No entanto, embalado por Ritalina, que me faz ter condições mentais de me concentrar na implementação deste meu produto, perco alguns quilogramas por não ter fome, pois meu único exercício tem sido o de paciência. Não saio de casa, apenas fico aqui, neste apartamento já imundo, trabalhando, trabalhando, para que outros, se apossem de minha virtude, de meu trabalho, de meu esforço, de meu tempo, de minha família, sem pagarem um puto por isso.
Me pergunto como eu tenho a imensa capacidade em me deixar ser aproveitado. O pior, é que por ser um produto – não é de forma alguma um trabalho no qual vendo minhas horas, é um trabalho no qual desenvolvo um produto e vendo o direito de usarem esse meu produto em um produto interno deles, e deveria ser somente um produto interno deles, mas tenho motivos, razões suficientes, para acreditar que querem e estão determinados, a se apossarem de meu produto, como se fossem deles, mesmo não o sendo. E se tivessem a determinação de me pagarem por isso, por abrir mão do que é meu, tudo bem, se entrássemos em acordo, mas não, querem simplesmente me pagar por um pão, mas com o objetivo de se apossarem da padaria.
Só tenho algo a dizer: NÂO VOU DEIXAR, NÃO DESSA VEZ, QUE FAÇAM ISSO!
Posso ter trabalhado em vão durante esses seis meses e estou disposto a não receber um tostão por isso, mas não vão levar o que é meu sem pagarem o preço disso.

Quer comprar a padaria? Eu vendo! Mas não pelo preço do pão!

Aproveito, para deixar uma pequena contribuição, minha, antes que seja morto por parasitas. Segue:

  1 package net.marciowb.bemtevi.gui.layer.s57;
  2 
  3 import com.solvoj.S52Library.S52Catalog;
  4 import com.solvoj.S52Library.S52DisplayCategoryEnum;
  5 import com.solvoj.S52Library.S52ObjectSymbolization;
  6 import com.solvoj.S52Library.S52ObjectType;
  7 import com.solvoj.S52Library.S52PresentationEnum;
  8 import com.solvoj.S52Library.S57AttributeEnum;
  9 import com.solvoj.poison.io.FlatFloatBuffer;
 10 import com.solvoj.poison.util.ListUtil;
 11 import java.io.File;
 12 import java.io.Serializable;
 13 import java.util.ArrayList;
 14 import java.util.List;
 15 import java.util.logging.Level;
 16 import java.util.logging.Logger;
 17 import net.sourceforge.capcode.S57Library.basics.Link;
 18 import net.sourceforge.capcode.S57Library.basics.S57Pos2D;
 19 import net.sourceforge.capcode.S57Library.objects.S57Attribute;
 20 import net.sourceforge.capcode.S57Library.objects.S57Edge;
 21 import net.sourceforge.capcode.S57Library.objects.S57Feature;
 22 import net.sourceforge.capcode.S57Library.objects.S57IsolatedNode;
 23 import net.sourceforge.capcode.S57Library.objects.S57ModuleReader;
 24 import net.sourceforge.capcode.S57Library.objects.S57ObjectsVector;
 25 import net.sourceforge.capcode.S57Library.objects.S57Spatial;
 26 
 27 /**
 28  * A {@link S57ModuleReader} specialized to aid S52 presentations.
 29  * 
 30  * @see S52Catalog
 31  *
 32  * @author Marcio Wesley Borges, Niterói-RJ, 2010-12-10
 33  */
 34 public class S57S52ModuleReader extends S57ModuleReader<S57S52ModuleReader.S52LikeFeature> {
 35 
 36     private static final Logger logger = Logger.getLogger(S57S52ModuleReader.class.getName());
 37     public static final float DEG2RAD = (float) (1.0 / 180.0 * Math.PI);
 38     private final S52Context scat;
 39     private List<S52LikeFeature> symbolizedFeatures = new ArrayList<S52LikeFeature>(3000);
 40     private final boolean finestIsLoggable;
 41 
 42     public S57S52ModuleReader(S52Context scat, File file) {
 43         super(file);
 44         this.scat = scat;
 45         finestIsLoggable = logger.isLoggable(Level.FINEST);
 46     }
 47 
 48     @Override
 49     protected S52LikeFeature createFeature() {
 50         return new S52LikeFeature();
 51     }
 52 
 53     @Override
 54     public S57ObjectsVector<S52LikeFeature> getFeatures() {
 55         return super.getFeatures();
 56     }
 57 
 58     private S52PresentationEnum getPresentation(S52LikeFeature fe) {
 59         switch (fe.getPrimitive()) {
 60             case Area:
 61                 return scat.getBoundariesPresentation().getPresentation();
 62             case Line:
 63                 return S52PresentationEnum.LINES;
 64             case Point:
 65                 return scat.getPointsPresentation().getPresentation();
 66         }
 67         return null;
 68     }
 69 
 70 
 71     private void posProcessArea(S52LikeFeature fe) {
 72         int numInteriorCoords = 0;
 73         int numCoords = 0;
 74         int numCloseds = 0;
 75         boolean lastLinkIsClosed = true;
 76         S57Pos2D firstBeginInterior = null;
 77         S57Pos2D firstBegin = null;
 78         S57Pos2D firstEnd = null;
 79         S57Pos2D lastBegin = null;
 80         S57Pos2D lastEnd = null;
 81         Link lastLink = null;
 82         Link firstInteriorLink = null;
 83         for (Link link : fe.getSpatials()) {
 84             final S57Edge node = (S57Edge) link.spatial;
 85             if (node.hasAttributes()) {
 86                 for (S57Attribute a : node.getAttributes()) {
 87                     if (a.type == null) {
 88                         continue;
 89                     }
 90                     switch (a.type) {
 91                         case QUAPOS://TODO Exibir quality of pos, como necessário.
 92                             continue;
 93                         default:
 94                             logger.log(Level.WARNING, "Node {0} com atributo!", node);
 95                     }
 96                 }
 97             }
 98 
 99             int addittionalSize = 2;
100 
101             S57Pos2D begin = node.getBegin().getCoordinate();
102             S57Pos2D end = node.getEnd().getCoordinate();
103             if (link.isReversed()) {
104                 final S57Pos2D tmp = begin;
105                 begin = end;
106                 end = tmp;
107             }
108             if (firstBegin == null) {
109                 firstBegin = begin;
110             }
111             assert (link != firstInteriorLink) || lastLinkIsClosed;
112             if (lastEnd == begin) {
113                 //logger.info("lastEnd == begin");
114                 addittionalSize--;
115             } else {
116                 assert lastLinkIsClosed : "{lastLinkIsClosed}";
117             }
118             lastLinkIsClosed = false;
119             if (firstBegin == end) {
120                 //logger.info(begin==firstBegin ? "Self closed" : "Closed");
121                 lastLinkIsClosed = true;
122             }
123             if (lastLinkIsClosed) {
124                 lastBegin = null;
125                 lastEnd = null;
126                 firstBegin = null;
127                 numCloseds++;
128             } else {
129                 lastBegin = begin;
130                 lastEnd = end;
131                 lastLink = link;
132             }
133 
134             final int len = (node.getCoordinates().size() + addittionalSize);
135             if (link.isInterior()) {
136                 if (firstInteriorLink == null) {
137                     firstInteriorLink = link;
138                 }
139                 numInteriorCoords += len;
140             } else {
141                 numCoords += len;
142             }
143 
144             assert numCloseds <= 1 || link.isInterior() :
145                     "{numCloseds<=1 || link.isInterior()}: numCloseds=" + numCloseds;
146         }
147         // assert (firstBeginInterior!=null) || lastEnd.equals(firstBegin) : "{(firstBeginInterior!=null) || lastEnd.equals(firstBegin)}; firstBeginInterior:" + firstBeginInterior + "; lastEnd:"+lastEnd+"; firstBegin:" + firstBegin;
148         assert numCloseds <= fe.getSpatials().size() : "{numCloseds<=fe.getLinkToSpatials().size()}; numCloseds=" + numCloseds + "; fe.getLinkToSpatials().size()=" + fe.
149                 getSpatials().size();
150 
151         if (numCoords > 0 || numInteriorCoords > 0) {
152 
153             final FlatFloatBuffer bufCoords = new FlatFloatBuffer((numInteriorCoords + numCoords) * 2, numCloseds);
154 
155             lastLink = null;
156             lastBegin = null;
157             lastEnd = null;
158             firstBegin = null;
159             int idxCoord = 0;
160             float[] coords = null;
161             for (Link link : fe.getSpatials()) {
162 
163                 final S57Edge node = (S57Edge) link.spatial;
164                 final int numPositions = node.getCoordinates().size();
165 
166                 S57Pos2D begin = node.getBegin().getCoordinate();
167                 S57Pos2D end = node.getEnd().getCoordinate();
168 
169                 int inc = 1;
170                 int idxPos = 0;
171                 if (link.isReversed()) {
172                     final S57Pos2D tmp = begin;
173                     begin = end;
174                     end = tmp;
175                     inc = -1;
176                     idxPos = numPositions - 1;
177                 }
178 
179                 if (lastLinkIsClosed) {
180                     coords = bufCoords.add(idxCoord);
181                 }
182 
183                 boolean skipBegin = false;
184                 boolean skipEnd = false;
185 
186                 if (firstBegin == null) {
187                     firstBegin = begin;
188                 }
189                 assert (link != firstInteriorLink) || lastLinkIsClosed;
190                 if (lastEnd == begin) {
191                     //logger.info("lastEnd == begin");
192                     skipBegin = true;
193                 } else {
194                     assert lastLinkIsClosed : "{lastLinkIsClosed}";
195                 }
196                 lastLinkIsClosed = false;
197                 if (firstBegin == end) {
198                     //logger.info(begin==firstBegin ? "Self closed" : "Closed");
199                     lastLinkIsClosed = true;
200                 }
201                 if (lastLinkIsClosed) {
202                     lastBegin = null;
203                     lastEnd = null;
204                     firstBegin = null;
205                     numCloseds--;
206                 } else {
207                     lastBegin = begin;
208                     lastEnd = end;
209                     lastLink = link;
210                 }
211 
212                 if (!skipBegin) {
213                     coords[idxCoord++] = begin.latitude * DEG2RAD;
214                     coords[idxCoord++] = begin.longitude * DEG2RAD;
215                 }
216                 for (int i = 0; i < numPositions; i++) {
217                     final S57Pos2D pos = node.getCoordinates().get(idxPos);
218                     coords[idxCoord++] = pos.latitude * DEG2RAD;
219                     coords[idxCoord++] = pos.longitude * DEG2RAD;
220                     idxPos += inc;
221                 }
222                 if (!skipEnd) {
223                     coords[idxCoord++] = end.latitude * DEG2RAD;
224                     coords[idxCoord++] = end.longitude * DEG2RAD;
225                 }
226             }//end for Link
227             assert numCloseds == 0 : "{numCloseds==0}" + "; numCloseds:" + numCloseds;
228 
229             fe.coordinates = bufCoords;
230         }
231     }
232 
233     private void posProcessLine(S52LikeFeature fe) {
234         int numCoords = 0;
235         S57Pos2D firstBegin = null;
236         S57Pos2D firstEnd = null;
237         S57Pos2D lastBegin = null;
238         S57Pos2D lastEnd = null;
239         Link lastLink = null;
240 
241         for (Link link : fe.getSpatials()) {
242             final S57Edge node = (S57Edge) link.spatial;
243             if (node.hasAttributes()) {
244                 for (S57Attribute a : node.getAttributes()) {
245                     if (a.type == null) {
246                         continue;
247                     }
248                     switch (a.type) {
249                         case QUAPOS://TODO Exibir quality of pos, como necessário.
250                             continue;
251                         default:
252                             logger.log(Level.WARNING, "Node {0} com atributo!", node);
253                     }
254                 }
255             }
256 
257             int addittionalSize = 2;
258 
259             S57Pos2D begin = node.getBegin().getCoordinate();
260             S57Pos2D end = node.getEnd().getCoordinate();
261             if (link.isReversed()) {
262                 final S57Pos2D tmp = begin;
263                 begin = end;
264                 end = tmp;
265             }
266             if (firstBegin == null) {
267                 firstBegin = begin;
268             }
269             lastBegin = begin;
270             lastEnd = end;
271             lastLink = link;
272 
273             final int len = (node.getCoordinates().size() + addittionalSize);
274             numCoords += len;
275         }
276 
277         if (numCoords > 0) {
278 
279             final FlatFloatBuffer bufCoords = new FlatFloatBuffer(numCoords * 2, fe.getSpatials().size());
280             final float[] coords = bufCoords.add(0);
281             lastLink = null;
282             lastBegin = null;
283             lastEnd = null;
284             firstBegin = null;
285             int idxCoord = 0;
286             for (Link link : fe.getSpatials()) {
287 
288                 final S57Edge node = (S57Edge) link.spatial;
289                 final int numPositions = node.getCoordinates().size();
290 
291                 S57Pos2D begin = node.getBegin().getCoordinate();
292                 S57Pos2D end = node.getEnd().getCoordinate();
293 
294                 int inc = 1;
295                 int idxPos = 0;
296                 if (link.isReversed()) {
297                     final S57Pos2D tmp = begin;
298                     begin = end;
299                     end = tmp;
300                     inc = -1;
301                     idxPos = numPositions - 1;
302                 }
303 
304                 lastBegin = begin;
305                 lastEnd = end;
306                 lastLink = link;
307 
308                 coords[idxCoord++] = begin.latitude * DEG2RAD;
309                 coords[idxCoord++] = begin.longitude * DEG2RAD;
310                 for (int i = 0; i < numPositions; i++) {
311                     final S57Pos2D pos = node.getCoordinates().get(idxPos);
312                     coords[idxCoord++] = pos.latitude * DEG2RAD;
313                     coords[idxCoord++] = pos.longitude * DEG2RAD;
314                     idxPos += inc;
315                 }
316                 coords[idxCoord++] = end.latitude * DEG2RAD;
317                 coords[idxCoord++] = end.longitude * DEG2RAD;
318             }//end for Link
319 
320             fe.coordinates = bufCoords;
321         }
322     }
323 
324     private void posProcessPoint(final S52LikeFeature fe) {
325         int numCoords = 0;
326         for (Link link : fe.getSpatials()) {
327             if (!(link.spatial instanceof S57IsolatedNode)) {
328                 //logger.log(Level.WARNING, "Trate o link {0} de um feature do tipo ponto com espacial: {1}.", new Object[]{link, link.spatial});
329                 //continue;
330                 assert (!ListUtil.isEmpty(link.spatial.getCoordinates())) : link;
331             }
332             final S57Spatial node = link.spatial;
333             if (node.hasAttributes()) {
334                 for (S57Attribute a : node.getAttributes()) {
335                     if (a.type == null) {
336                         continue;
337                     }
338                     switch (a.type) {
339                         case QUAPOS://TODO Exibir quality of pos, como necessário.
340                             continue;
341                         default:
342                             logger.log(Level.WARNING, "Node {0} com atributo!", node);
343                     }
344                 }
345             }
346 
347             final int len = node.getCoordinates().size();
348             numCoords += len;
349         }
350 
351         if (numCoords > 0) {
352 
353             final FlatFloatBuffer bufCoords = new FlatFloatBuffer(numCoords * 2, fe.getSpatials().size());
354             final float[] coords = bufCoords.add(0);
355             int idxCoord = 0;
356             for (Link link : fe.getSpatials()) {
357                 final S57Spatial node = link.spatial;
358                 for (S57Pos2D pos: node.getCoordinates()) {
359                     coords[idxCoord++] = pos.latitude * DEG2RAD;
360                     coords[idxCoord++] = pos.longitude * DEG2RAD;
361                 }
362             }//end for Link
363 
364             fe.coordinates = bufCoords;
365         }
366     }
367 
368     private void posProcessSymbolization(final S52LikeFeature fe) {
369         fe.symbolization = null;
370         fe.priority = Integer.MIN_VALUE;
371         final S52PresentationEnum presentation = getPresentation(fe);
372         final S52DisplayCategoryEnum category = scat.getDisplayCategory();
373         for (S52ObjectSymbolization symb : fe.object.getSymbolizations(presentation, category)) {
374             if (fe.combines(symb)) {
375                 fe.symbolization = symb;
376 
377                 //Set Priority
378                 int priority = symb.priority * 100; //the default priority
379                 if (fe.isGroup1()) { // S57 group 1 filled areas
380                     priority+=10;
381                 } else if (fe.isArea()) {
382                     priority+=20;
383                 } else if (fe.isLine()) {
384                     priority+=30;
385                 } else if (fe.isPoint()) {
386                     priority+=40;
387                 }
388                 fe.priority = priority;
389 
390                 break;
391             }
392         }
393         if (finestIsLoggable) {
394             logger.log(Level.FINEST, "For the feature {0} the following symbolization matched: {1}.", new Object[]{fe, fe.symbolization});
395         }
396     }
397 
398     /**
399      * 

4.7.2 Feature record to spatial record pointer fielduse by line features 400 * In order to facilitate the decoding of linear features comprising multiple edges, the vector records 401 * making up the linear feature must be referenced sequentially.
402 * The direction in which an edge is to be interpreted for a particular linear feature may be important 403 * (e.g. for the symbolization of non-symmetric line styles). In such cases the direction of 404 * interpretation is indicated in the ORNT subfield (see figure 4.1). Permissible values are:
405 * F {1} Forward.
406 * R {2} Reverse.
407 * N {255} Direction is not relevant.

408 * 409 *

The exterior boundary must be completely encoded before any interior boundaries and each interior 410 * boundary must be completed before encoding further interior boundaries.

411 * 412 */ 413 @Override 414 protected void posProcessFeature(final S52LikeFeature fe) { 415 if (fe.object==null) { 416 final S52ObjectType obj = scat.getObject(fe.getType()); 417 fe.object = obj; 418 } 419 420 posProcessSymbolization(fe); 421 if (fe.symbolization == null) { 422 logger.log(Level.FINEST, "N\u00e3o foi encontrada simboliza\u00e7\u00e3o para {0}", fe); 423 return; 424 } 425 426 if (fe.coordinates==null) { 427 if (fe.isArea()) { 428 posProcessArea(fe); 429 } else if (fe.isLine()) { 430 posProcessLine(fe); 431 } else if (fe.isPoint()) { 432 posProcessPoint(fe); 433 } 434 } 435 436 if (fe.coordinates!=null) { 437 symbolizedFeatures.add(fe); 438 } 439 } 440 441 public List<S52LikeFeature> getSymbolizedFeatures() { 442 if (symbolizedFeatures.isEmpty()) { 443 for (S52LikeFeature fe: features) { 444 posProcessFeature(fe); 445 } 446 } 447 return symbolizedFeatures; 448 } 449 450 public void invalidateSymbolizedFeatures() { 451 symbolizedFeatures.clear(); 452 } 453 454 public class S52LikeFeature extends S57Feature { 455 456 S52ObjectSymbolization symbolization; 457 S52ObjectType object; 458 FlatFloatBuffer coordinates; 459 int priority = Integer.MIN_VALUE; 460 461 public float[] getEdgeCoordinates() { 462 return coordinates == null ? null : coordinates.getData(); 463 } 464 465 public boolean isAssHole() { 466 return coordinates != null && coordinates.count() > 1; 467 } 468 469 public FlatFloatBuffer getCoordinates() { 470 return coordinates; 471 } 472 473 public S52ObjectSymbolization getSymbolization() { 474 return symbolization; 475 } 476 477 public S52ObjectType getObject() { 478 return object; 479 } 480 481 boolean combines(final S52ObjectSymbolization symb) { 482 if (!symb.object.equals(object)) { 483 return false; 484 } 485 486 if (!symb.presentation.applies(primitive)) { 487 return false; 488 } 489 490 if (!symb.isCombined()) { 491 return true; 492 } 493 494 if (attributes == null) { 495 return false; 496 } 497 498 int numSymbAttributes = symb.combination.getAttributeCount(); 499 if (attributes.size() < numSymbAttributes) {//Only continues if the feature has the same or more attributes than the symbolization rule. 500 return false; 501 } 502 503 for (S57Attribute a : attributes) { 504 if (!symb.combination.hasAttribute(a.type)) { 505 continue; 506 } 507 final Serializable value = a.getTypedValue(); 508 if (!symb.combination.anyCombination(a.type, value)) { 509 return false; 510 } 511 if (--numSymbAttributes==0) { 512 return true; 513 } 514 } 515 return numSymbAttributes==0; 516 } 517 518 public int getPriority() { 519 return priority; 520 } 521 522 /** 523 * Build a list of "associated" DEPARE and DRGARE objects from a given 524 * object. to be "associated" means to be physically intersecting, 525 * overlapping, or contained within, depending upon the geometry type 526 * of the given object. 527 */ 528 public List<S52LikeFeature> getAssociatedObjects() { 529 int j; 530 int disPrioIdx; 531 532 final List<S52LikeFeature> pobj_list = new ArrayList<S52LikeFeature>(); 533 pobj_list.clear(); 534 535 float lat, lon; 536 fromSM((obj->x * obj->x_rate) + obj->x_origin, (obj->y * obj->y_rate) + obj->y_origin, ref_lat, ref_lon, &lat, &lon); 537 // What is the entry object geometry type? 538 539 if (isPoint()) { 540 ObjRazRules *top; 541 disPrioIdx = 1; 542 543 for(j=0 ; j<LUPNAME_NUM ; j++) { 544 545 top = razRules[disPrioIdx][j]; 546 while ( top != NULL) { 547 548 if(!strncmp(top->obj->FeatureName, "DEPARE", 6) || !strncmp(top->obj->FeatureName, "DRGARE", 6)) { 549 if(obj->BBObj.PointInBox( lon, lat, 0.0)) 550 { 551 if(IsPointInObjArea(lat, lon, 0.0, top->obj)) 552 pobj_list.add(top->obj); 553 } 554 } 555 556 ObjRazRules *nxx = top->next; 557 top = nxx; 558 } 559 }//end for 560 }//end if isPoint 561 return pobj_list; 562 }//end of method getAssociatedObjects 563 564 }//end of class S52LikeFeature 565 566 }//end of class S57S52ModuleReader 567 568 569
Posted in development, it, java, projects | 1 Comment

BeanShell: Patch to do it work with enum in switch/case statements

I really like BeanShell. But it is based in pre-Java1.5. So, there is some new Java language features, like enumerations and generics, that it doesn’t allow to use. I have made a plugin to use it with Netbeans and I have included it in several projects that I develop, since 2006/2007, I think. I use it into my applications like I use VBA into Microsoft Office stuffs. With BeanShell is easy to do several things without recompile or shutdown your application. You can query, prototype, test, simulate, plugin, plugout, automate and do many things with it. I really like it. I know that Java have has officially incorporated support to scripting, but BeanShell is Java… so if I need to reuse my code later in Java, is simple as to do: nothing! Yeah, it’s true. ’cause bsh can load .java file sources, also. So, the sky isn’t the limit; the limit is you!

BeanShell Patch to work with enums in switch statements

The patch is to be applied into the file class BSHSwitchStatement. See bellow just the lines affected by it:

 62         //marciowb 2010-12-11 patch to allows the Bsh to works with enum with switchs statements.
 63         //it breaks the compatibility versions of Java older than 1.5, but it works.
 64         final Class switchValClass = switchVal.getClass();               //What's the class of the value of this switch?
 65         final boolean switchValIsEnumType = switchValClass.isEnum();     //Is it a enum type?
 66 
 67         // while more labels or blocks and haven't hit return control
 68         while (child < numchild && returnControl == null) {
 69 
 70             Object labelValue = label.isDefault ? null
 71                                 : (switchValIsEnumType //If it is a enum type,
 72                                    ? Enum.valueOf(switchValClass, label.getChild(0).getText().trim()) //then get the enum value
 73                                    : label.eval(callstack, interpreter));                //otherwise, gonna default evaluation...
 74 
 75             // if label is default or equals switchVal
 76             if (label.isDefault || primitiveEquals(switchVal, labelValue, callstack, switchExp)) {

Now, if you want the entire file:

  1 /*****************************************************************************
  2  *                                                                           *
  3  *  This file is part of the BeanShell Java Scripting distribution.          *
  4  *  Documentation and updates may be found at http://www.beanshell.org/      *
  5  *                                                                           *
  6  *  Sun Public License Notice:                                               *
  7  *                                                                           *
  8  *  The contents of this file are subject to the Sun Public License Version  *
  9  *  1.0 (the "License"); you may not use this file except in compliance with *
 10  *  the License. A copy of the License is available at http://www.sun.com    * 
 11  *                                                                           *
 12  *  The Original Code is BeanShell. The Initial Developer of the Original    *
 13  *  Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright     *
 14  *  (C) 2000.  All Rights Reserved.                                          *
 15  *                                                                           *
 16  *  GNU Public License Notice:                                               *
 17  *                                                                           *
 18  *  Alternatively, the contents of this file may be used under the terms of  *
 19  *  the GNU Lesser General Public License (the "LGPL"), in which case the    *
 20  *  provisions of LGPL are applicable instead of those above. If you wish to *
 21  *  allow use of your version of this file only under the  terms of the LGPL *
 22  *  and not to allow others to use your version of this file under the SPL,  *
 23  *  indicate your decision by deleting the provisions above and replace      *
 24  *  them with the notice and other provisions required by the LGPL.  If you  *
 25  *  do not delete the provisions above, a recipient may use your version of  *
 26  *  this file under either the SPL or the LGPL.                              *
 27  *                                                                           *
 28  *  Patrick Niemeyer (pat@pat.net)                                           *
 29  *  Author of Learning Java, O'Reilly & Associates                           *
 30  *  http://www.pat.net/~pat/                                                 *
 31  *                                                                           *
 32  *****************************************************************************/
 33 package bsh;
 34 
 35 public class BSHSwitchStatement extends SimpleNode implements ParserConstants {
 36 
 37     public BSHSwitchStatement(int id) {
 38         super(id);
 39     }
 40 
 41     public Object eval(CallStack callstack, Interpreter interpreter) throws EvalError {
 42         int numchild = jjtGetNumChildren();
 43         int child = 0;
 44         SimpleNode switchExp = ((SimpleNode) jjtGetChild(child++));
 45         Object switchVal = switchExp.eval(callstack, interpreter);
 46 
 47         /*
 48         Note: this could be made clearer by adding an inner class for the
 49         cases and an object context for the child traversal.
 50          */
 51         // first label
 52         BSHSwitchLabel label;
 53         Object node;
 54         ReturnControl returnControl = null;
 55 
 56         // get the first label
 57         if (child >= numchild) {
 58             throw new EvalError("Empty switch statement.", this, callstack);
 59         }
 60         label = ((BSHSwitchLabel) jjtGetChild(child++));
 61 
 62         //marciowb 2010-12-11 patch to allows the Bsh to works with enum with switchs statements.
 63         //it breaks the compatibility versions of Java older than 1.5, but it works.
 64         final Class switchValClass = switchVal.getClass();               //What's the class of the value of this switch?
 65         final boolean switchValIsEnumType = switchValClass.isEnum();     //Is it a enum type?
 66 
 67         // while more labels or blocks and haven't hit return control
 68         while (child < numchild && returnControl == null) {
 69 
 70             Object labelValue = label.isDefault ? null
 71                                 : (switchValIsEnumType //If it is a enum type,
 72                                    ? Enum.valueOf(switchValClass, label.getChild(0).getText().trim()) //then get the enum value
 73                                    : label.eval(callstack, interpreter));                //otherwise, gonna default evaluation...
 74 
 75             // if label is default or equals switchVal
 76             if (label.isDefault || primitiveEquals(switchVal, labelValue, callstack, switchExp)) {
 77                 // execute nodes, skipping labels, until a break or return
 78                 while (child < numchild) {
 79                     node = jjtGetChild(child++);
 80                     if (node instanceof BSHSwitchLabel) {
 81                         continue;
 82                     }
 83                     // eval it
 84                     Object value = ((SimpleNode) node).eval(callstack, interpreter);
 85 
 86                     // should check to disallow continue here?
 87                     if (value instanceof ReturnControl) {
 88                         returnControl = (ReturnControl) value;
 89                         break;
 90                     }
 91                 }
 92             } else {
 93                 // skip nodes until next label
 94                 while (child < numchild) {
 95                     node = jjtGetChild(child++);
 96                     if (node instanceof BSHSwitchLabel) {
 97                         label = (BSHSwitchLabel) node;
 98                         break;
 99                     }
100                 }
101             }
102         }
103 
104         if (returnControl != null && returnControl.kind == RETURN) {
105             return returnControl;
106         } else {
107             return Primitive.VOID;
108         }
109     }
110 
111     /**
112     Helper method for testing equals on two primitive or boxable objects.
113     yuck: factor this out into Primitive.java
114      */
115     private boolean primitiveEquals(Object switchVal, Object targetVal, CallStack callstack,
116                                     SimpleNode switchExp)
117             throws EvalError {
118         if (switchVal instanceof Primitive || targetVal instanceof Primitive) {
119             try {
120                 // binaryOperation can return Primitive or wrapper type
121                 Object result = Primitive.binaryOperation(
122                         switchVal, targetVal, ParserConstants.EQ);
123                 result = Primitive.unwrap(result);
124                 return result.equals(Boolean.TRUE);
125             } catch (UtilEvalError e) {
126                 throw e.toEvalError(
127                         "Switch value: " + switchExp.getText() + ": ",
128                         this, callstack);
129             }
130         } else {
131             return switchVal.equals(targetVal);
132         }
133     }
134 }
135 
136 

Using the patch, you can use codes like this:

        for (S52DrawInstruction ins : drawIns) {
            switch (ins.getType()) { 
                case AC:
                    final S52DrawAreaColor ac = (S52DrawAreaColor) ins;
                    poly.setFillPaint(ac.color.color);
                    break;

                case LS:
                    final S52DrawLineStyle ls = (S52DrawLineStyle) ins;
                    poly.setLinePaint(ls.color.color);
                    BasicStroke stroke;
                    stroke = helper.getStroke(ls.width, ls.style);
                    poly.setStroke(stroke);
                    break;

                case CS:
                    final S52DrawComplex cs = (S52DrawComplex) ins;
                    final String simpleInstructions = getDrawInstructionsFromComplex(fe, cs);
                    if (simpleInstructions == null) {
                        info("Não obtívemos as instruções de desenho simplificadas para a instrução complexa: " + cs + " de " + fe);
                        return;
                    }
                    final S52DrawInstructions simpleDrawIns = scat.parseDrawInstructions(simpleInstructions);
                    configGraphic(poly, fe, co, symb, sp, simpleDrawIns);
                    break;

                default:
                    info("Simbologia não tratada: " + ins);
            }
        }

Where the method getType() returns a enum value from the enumeration bellow:

 1 package com.solvoj.S52Library;
 2 
 3 /**
 4  * List of S52 Symbolization Instruction.
 5  *
 6  * @author Marcio Wesley Borges, 2010-12-07 Niterói-RJ
 7  */
 8 public enum S52DrawInstructionEnum {
 9 
10     /** line drawing styles (simple ones (LS) for variable width coloured lines */
11     LS(new S52DrawLineStyle.Factory()),
12     /** complex line-styles (LC), where line is visualized by a repetitive pattern) */
13     LC(new S52DrawLineComplex.Factory()),
14     /** area fill instructions, either with one color (AC, four levels of transparency) */
15     AC(new S52DrawAreaColor.Factory()),
16     /** area with repetition of pattern symbols (AP). */
17     AP(new S52DrawAreaPattern.Factory()),
18     /** symbols for point locationsSY. */
19     SY(new S52DrawSymbol.Factory()),
20     /** text drawing instructions with selectable font family, size, and justification */
21     TX(new S52DrawText.Factory()),
22     /** text drawing instructions with selectable font family, size, and justification */
23     TE(new S52DrawTextExtended.Factory()),
24     /**
25      * complex symbolization primitives (CS) provide hooks for external functions (from a
26      * dynamically loadable library) that can provide more complicated symbolizations than
27      * the look-up tables of the presentation library. Complex symbolization procedures can
28      * also take advantage on the user-changeable presentation parameters for providing more
29      * dynamic visualization.
30      */
31     CS(new S52DrawComplex.Factory());
32 
33     final S52DrawInstruction.Factory<?> factory;
34 
35     private S52DrawInstructionEnum(S52DrawInstruction.Factory<?> instructionParser) {
36         this.factory = instructionParser;
37     }
38 
39     public S52DrawInstruction.Factory<?> getFactory() {
40         return factory;
41     }
42 
43     @Override
44     public String toString() {
45         return name() + " class " + getFactory().getClass().getEnclosingClass().getSimpleName();
46     }
47 }//end of enum S52DrawInstructionEnum
48 
49 
Posted in development, it, java | Leave a comment

Glassfish Embedded Server Mode Doesn’t Works out of Internet

The First Problem: Without Internet

Using Glassfish 3.0.1 Embedded, while I was without Internet, I got the following error while starting the GF3 Embedded server:

Aug 1, 2010 8:32:18 PM org.apache.catalina.core.StandardContext callServletContainerInitializers
SEVERE: PWC1420: Error invoking ServletContainerInitializer org.apache.jasper.runtime.TldScanner
org.apache.jasper.JasperException: PWC6177: XML parsing error on file file:/E:/downloads/devel/java/glassfish/glassfish-embedded-all-3.0.1.jar
org.apache.jasper.JasperException: PWC6177: XML parsing error on file 
        at org.apache.jasper.xmlparser.ParserUtils.parseXMLDocument(ParserUtils.java:325)
        at org.apache.jasper.xmlparser.ParserUtils.parseXMLDocument(ParserUtils.java:366)
        at org.apache.jasper.runtime.TldScanner.scanTld(TldScanner.java:526)
        at org.apache.jasper.runtime.TldScanner.scanJar(TldScanner.java:369)
        at org.apache.jasper.runtime.TldScanner.scanJars(TldScanner.java:633)
        at org.apache.jasper.runtime.TldScanner.scanTlds(TldScanner.java:282)
        at org.apache.jasper.runtime.TldScanner.onStartup(TldScanner.java:228)
        at org.apache.catalina.core.StandardContext.callServletContainerInitializers(StandardContext.java:5352)
        at com.sun.enterprise.web.WebModule.callServletContainerInitializers(WebModule.java:550)
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:5263)
        at com.sun.enterprise.web.WebModule.start(WebModule.java:499)
        at org.apache.catalina.core.ContainerBase.startChildren(ContainerBase.java:1523)
        at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1190)
        at org.apache.catalina.core.StandardHost.start(StandardHost.java:975)
        at org.apache.catalina.core.ContainerBase.startChildren(ContainerBase.java:1523)
        at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1190)
        at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:529)
        at org.apache.catalina.startup.Embedded.start(Embedded.java:946)
        at com.sun.enterprise.web.WebContainer.postConstruct(WebContainer.java:591)
        at com.sun.hk2.component.AbstractWombImpl.inject(AbstractWombImpl.java:174)
        at com.sun.hk2.component.ConstructorWomb$1.run(ConstructorWomb.java:87)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.hk2.component.ConstructorWomb.initialize(ConstructorWomb.java:84)
        at com.sun.hk2.component.AbstractWombImpl.get(AbstractWombImpl.java:77)
        at com.sun.hk2.component.SingletonInhabitant.get(SingletonInhabitant.java:58)
        at com.sun.hk2.component.LazyInhabitant.get(LazyInhabitant.java:107)
        at com.sun.hk2.component.AbstractInhabitantImpl.get(AbstractInhabitantImpl.java:60)
        at org.glassfish.internal.data.EngineInfo.getContainer(EngineInfo.java:78)
        at com.sun.enterprise.v3.server.ApplicationLifecycle.startContainers(ApplicationLifecycle.java:716)
        at com.sun.enterprise.v3.server.ApplicationLifecycle.setupContainerInfos(ApplicationLifecycle.java:461)
        at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:262)
        at com.sun.enterprise.v3.server.ApplicationLoaderService.processApplication(ApplicationLoaderService.java:362)
        at com.sun.enterprise.v3.server.ApplicationLoaderService.postConstruct(ApplicationLoaderService.java:185)
        at com.sun.hk2.component.AbstractWombImpl.inject(AbstractWombImpl.java:174)
        at com.sun.hk2.component.ConstructorWomb$1.run(ConstructorWomb.java:87)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.hk2.component.ConstructorWomb.initialize(ConstructorWomb.java:84)
        at com.sun.hk2.component.AbstractWombImpl.get(AbstractWombImpl.java:77)
        at com.sun.hk2.component.SingletonInhabitant.get(SingletonInhabitant.java:58)
        at com.sun.hk2.component.LazyInhabitant.get(LazyInhabitant.java:107)
        at com.sun.hk2.component.AbstractInhabitantImpl.get(AbstractInhabitantImpl.java:60)
        at com.sun.enterprise.v3.server.AppServerStartup.run(AppServerStartup.java:236)
        at com.sun.enterprise.v3.server.AppServerStartup.start(AppServerStartup.java:128)
        at com.sun.enterprise.module.bootstrap.Main.launch(Main.java:457)
        at com.sun.enterprise.module.bootstrap.Main.launch(Main.java:401)
        at com.sun.enterprise.module.bootstrap.Main.launch(Main.java:310)
        at com.sun.enterprise.module.bootstrap.Main.launch(Main.java:303)
        at com.sun.enterprise.glassfish.bootstrap.ASEmbedded.run(ASEmbedded.java:101)
        at com.sun.enterprise.glassfish.bootstrap.AbstractMain.start(AbstractMain.java:78)
        at org.glassfish.api.embedded.Server.(Server.java:288)
        at org.glassfish.api.embedded.Server.(Server.java:61)
        at org.glassfish.api.embedded.Server$Builder.build(Server.java:158)
        at org.glassfish.api.embedded.Server$Builder.build(Server.java:140)
        at net.marciowb.bemtevi.google.WebServer.install(WebServer.java:68)
        at net.marciowb.bemtevi.google.WebServer.start(WebServer.java:87)
        at net.marciowb.bemtevi.google.GoogleEarthServer.setRunning(GoogleEarthServer.java:175)
        at net.marciowb.bemtevi.google.GoogleEarthServer.init(GoogleEarthServer.java:116)
        at net.marciowb.bemtevi.BemTeVi$InitWebServer.run(BemTeVi.java:1073)
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
        at java.lang.Thread.run(Thread.java:619)
Caused by: java.net.UnknownHostException: java.sun.com
        at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:177)
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
        at java.net.Socket.connect(Socket.java:529)
        at java.net.Socket.connect(Socket.java:478)
        at sun.net.NetworkClient.doConnect(NetworkClient.java:163)
        at sun.net.www.http.HttpClient.openServer(HttpClient.java:394)
        at sun.net.www.http.HttpClient.openServer(HttpClient.java:529)
        at sun.net.www.http.HttpClient.(HttpClient.java:233)
        at sun.net.www.http.HttpClient.New(HttpClient.java:306)
        at sun.net.www.http.HttpClient.New(HttpClient.java:323)
        at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:860)
        at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:801)
        at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:726)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1049)
        at java.net.URL.openStream(URL.java:1010)
        at org.apache.xerces.impl.XMLEntityManager.setupCurrentEntity(Unknown Source)
        at org.apache.xerces.impl.XMLEntityManager.startEntity(Unknown Source)
        at org.apache.xerces.impl.XMLEntityManager.startDTDEntity(Unknown Source)
        at org.apache.xerces.impl.XMLDTDScannerImpl.setInputSource(Unknown Source)
        at org.apache.xerces.impl.XMLDocumentScannerImpl$DTDDispatcher.dispatch(Unknown Source)
        at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
        at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
        at org.apache.xerces.parsers.DTDConfiguration.parse(Unknown Source)
        at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
        at org.apache.xerces.parsers.DOMParser.parse(Unknown Source)
        at org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source)
        at org.apache.jasper.xmlparser.ParserUtils.parseXMLDocument(ParserUtils.java:296)
        ... 60 more

I google the error and I found the following references to it:

But none solution was found. So, as my system needs to run out of Internet, (and I after vote to fix the GF Issue #11516) I investigate the error in deep and I found the cause :).

The Diagnostic

GF3 XML Schema Path
The GF uses a special version (different from the used by Tomcat 5 or 6) of the class org.apache.jasper.xmlparser.ParserUtils and it (locally) “caches” the dtd and schemas files used by the Jasper version of the GF JSP 2.1. If you have installed GlassFish 3 you will find this DTD and schemas files at: \gf3-installation-dir\glassfish\lib\dtds and \gf3-installation-dir\glassfish\lib\schemas. This files isn’t into GF Embedded version package glassfish-embedded-all-3.0.1.jar, neither glassfish-embedded-web-3.0.1.jar. So you will to deploy it with GF Embedded Server or accept to download it from the Internet as always is happens with the GF Embedded version. But if you try to put this schemas files with your server, don’t matter where you try, nevertheless it doesn’t work. So, why?

At runtime I inspected the program putting a breakpoint in the throwing error point at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:177) and using Netbeans I saw that the environment of ParserUtils was:

CACHED_DTD_PUBLIC_IDS] String[] #6043(length=4) #6043(length=4) 
[0]] String "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" 
[1]] String "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" 
[2]] String "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" 
[3]] String "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
CACHED_DTD_RESOURCE_PATHS] String[] #6044(length=4) #6044(length=4) 
[0]] String "file:/E:/embedded-glassfish/domains/lib/dtdsweb-jsptaglibrary_1_1.dtd" "file:/E:/embedded-glassfish/domains/lib/dtdsweb-jsptaglibrary_1_1.dtd" 
[1]] String "file:/E:/embedded-glassfish/domains/lib/dtdsweb-jsptaglibrary_1_2.dtd" "file:/E:/embedded-glassfish/domains/lib/dtdsweb-jsptaglibrary_1_2.dtd" 
[2]] String "file:/E:/embedded-glassfish/domains/lib/dtdsweb-app_2_2.dtd" "file:/E:/embedded-glassfish/domains/lib/dtdsweb-app_2_2.dtd" 
[3]] String "file:/E:/embedded-glassfish/domains/lib/dtdsweb-app_2_3.dtd" "file:/E:/embedded-glassfish/domains/lib/dtdsweb-app_2_3.dtd" 
CACHED_SCHEMA_RESOURCE_PATHS] String[] #6045(length=4) #6045(length=4) 
[0]] String "file:/E:/embedded-glassfish/domains/lib/schemasweb-jsptaglibrary_2_0.xsd" "file:/E:/embedded-glassfish/domains/lib/schemasweb-jsptaglibrary_2_0.xsd" 
[1]] String "file:/E:/embedded-glassfish/domains/lib/schemasweb-jsptaglibrary_2_1.xsd" "file:/E:/embedded-glassfish/domains/lib/schemasweb-jsptaglibrary_2_1.xsd" 
[2]] String "file:/E:/embedded-glassfish/domains/lib/schemasweb-app_2_4.xsd" "file:/E:/embedded-glassfish/domains/lib/schemasweb-app_2_4.xsd" 
[3]] String "file:/E:/embedded-glassfish/domains/lib/schemasweb-app_2_5.xsd" "file:/E:/embedded-glassfish/domains/lib/schemasweb-app_2_5.xsd" 
DEFAULT_DTD_RESOURCE_PATHS] String[] #6044(length=4) #6044(length=4) 
[0]] String "file:/E:/embedded-glassfish/domains/lib/dtdsweb-jsptaglibrary_1_1.dtd" "file:/E:/embedded-glassfish/domains/lib/dtdsweb-jsptaglibrary_1_1.dtd" 
[1]] String "file:/E:/embedded-glassfish/domains/lib/dtdsweb-jsptaglibrary_1_2.dtd" "file:/E:/embedded-glassfish/domains/lib/dtdsweb-jsptaglibrary_1_2.dtd" 
[2]] String "file:/E:/embedded-glassfish/domains/lib/dtdsweb-app_2_2.dtd" "file:/E:/embedded-glassfish/domains/lib/dtdsweb-app_2_2.dtd" 
[3]] String "file:/E:/embedded-glassfish/domains/lib/dtdsweb-app_2_3.dtd" "file:/E:/embedded-glassfish/domains/lib/dtdsweb-app_2_3.dtd" 
DEFAULT_SCHEMA_RESOURCE_PATHS] String[] #6045(length=4) #6045(length=4) 
[0]] String "file:/E:/embedded-glassfish/domains/lib/schemasweb-jsptaglibrary_2_0.xsd" "file:/E:/embedded-glassfish/domains/lib/schemasweb-jsptaglibrary_2_0.xsd" 
[1]] String "file:/E:/embedded-glassfish/domains/lib/schemasweb-jsptaglibrary_2_1.xsd" "file:/E:/embedded-glassfish/domains/lib/schemasweb-jsptaglibrary_2_1.xsd" 
[2]] String "file:/E:/embedded-glassfish/domains/lib/schemasweb-app_2_4.xsd" "file:/E:/embedded-glassfish/domains/lib/schemasweb-app_2_4.xsd" 
[3]] String "file:/E:/embedded-glassfish/domains/lib/schemasweb-app_2_5.xsd" "file:/E:/embedded-glassfish/domains/lib/schemasweb-app_2_5.xsd" 
SCHEMA_LOCATION_ATTR] String "schemaLocation" "schemaLocation" 
dtdResourcePrefix] String "file:/E:/embedded-glassfish/domains/lib/dtds" "file:/E:/embedded-glassfish/domains/lib/dtds" 
entityResolver] WebEntityResolver #6038 org.glassfish.web.WebEntityResolver@7a7502f6 
dtdDir] File #6068 E:\embedded-glassfish\domains\lib\dtds 
knownDTDs] HashMap "size = 4" "size = 4" 
schemaDir] File #6067 E:\embedded-glassfish\domains\lib\schemas 
serverContext] ServerContextImpl #6066 com.sun.enterprise.v3.server.ServerContextImpl@2c54366 
errorHandler] MyErrorHandler #6037 org.apache.jasper.xmlparser.MyErrorHandler@35563b4b 
isDtdResourcePrefixFileUrl] boolean true true 
isSchemaResourcePrefixFileUrl] boolean true true 
log] Logger #6036 java.util.logging.Logger@54697123 
schemaCache] HashMap "size = 0" "size = 0" 
schemaResourcePrefix] String "file:/E:/embedded-glassfish/domains/lib/schemas" "file:/E:/embedded-glassfish/domains/lib/schemas" 
uri] String "file:/E:/downloads/devel/java/glassfish/glassfish-embedded-all-3.0.1.jar" "file:/E:/downloads/devel/java/glassfish/glassfish-embedded-all-3.0.1.jar" 
validate] boolean false false

Take a look again at:

CACHED_SCHEMA_RESOURCE_PATHS
"file:/E:/embedded-glassfish/domains/lib/schemasweb-jsptaglibrary_2_0.xsd" 
"file:/E:/embedded-glassfish/domains/lib/schemasweb-jsptaglibrary_2_1.xsd" 
"file:/E:/embedded-glassfish/domains/lib/schemasweb-app_2_4.xsd" 
"file:/E:/embedded-glassfish/domains/lib/schemasweb-app_2_5.xsd"

Saw? The filenames is wrong! The CACHED_SCHEMA_RESOURCE_PATHS variable is from ParserUtils and also schemaResourcePrefix that contains the value "file:/E:/embedded-glassfish/domains/lib/schemas". The value is correct, but this variable is a String type. So, I thought the error was in the code: probably the programmer was concatenating this string with the schema filename.

I download the source code (not exactly the same source code used by the GF3, but it helped) of the ParserUtils used by the GF3 Embedded server and I found that there are at less two important variables that regulates where lives the local schema files: schemaResourcePrefix and dtdResourcePrefix. So we must find who is setting this variable. Looking at the source code, I found the method public static void setDtdResourcePrefix(String prefix) and:

146     /**
147      * Sets the path prefix for .xsd resources
148      */
149     public static void setSchemaResourcePrefix(String prefix) {
150 
151         schemaResourcePrefix = prefix;
152 
153         for (int i=0; i<CACHED_SCHEMA_RESOURCE_PATHS.length; i++) {
154             String path = CACHED_SCHEMA_RESOURCE_PATHS[i];
155             int index = path.lastIndexOf('/');
156             if (index != -1) {
157                 CACHED_SCHEMA_RESOURCE_PATHS[i] =
158                     prefix + path.substring(index+1);
159             }
160         }
161     }
162 

After investigating who is calling these methods, I found it: com.sun.enterprise.web.WebContainer! See:

409         // TODO: ParserUtils should become a @Service and it should initialize itself.
410         // TODO: there should be only one EntityResolver for both DigesterFactory
411         // and ParserUtils
412         File root = _serverContext.getInstallRoot();
413         File libRoot = new File(root, "lib");
414         File schemas = new File(libRoot, "schemas");
415         File dtds = new File(libRoot, "dtds");
416 
417         try {
418             ParserUtils.setSchemaResourcePrefix(schemas.toURI().toURL().toString());
419             ParserUtils.setDtdResourcePrefix(dtds.toURI().toURL().toString());
420             ParserUtils.setEntityResolver(habitat.getComponent(EntityResolver.class, "web"));
421         } catch(MalformedURLException e) {
422             _logger.log(Level.SEVERE, "webContainer.exceptionSetSchemasDtdsLocation", e);
423         }
424 

The Solution

So I just patched it adding a back slash to these path values. But thinking better, the error is in ParserUtils, because it must checks for the backslash and will add it if needed. But as I don’t have the correct source code of ParserUtils (Guys, I hate Maven!!! What is wrong with Ant?!! Is it a new way to hide the things? What about is going with the open nature of the open source code? Oracle, IBM, are you listenning me?), I patched the WebContainer to:

412         File root = _serverContext.getInstallRoot();
413         File libRoot = new File(root, "lib");
414         File schemas = new File(libRoot, "schemas");
415         File dtds = new File(libRoot, "dtds");
416 
417         try {
418 ParserUtils.setSchemaResourcePrefix(schemas.toURI().toURL().toString() + "/"); 419 ParserUtils.setDtdResourcePrefix(dtds.toURI().toURL().toString() + "/");
420 ParserUtils.setEntityResolver(habitat.getComponent(EntityResolver.class, "web")); 421 } catch(MalformedURLException e) { 422 _logger.log(Level.SEVERE, "webContainer.exceptionSetSchemasDtdsLocation", e); 423 } 424

To apply it, copy the WebContainer code, change it with the patch above and compile it using the glassfish-embedded-all-3.0.1.jar. Adds it to your project as a library and reference it before start the server (I just use String.valueOf(WebContainer.DISPATCHER_MAX_DEPTH) to forces the JVM to load and init my patched WebContainer class before to start the GF thinks).

Putting the Schemas

lib directory from my gf3 embedded domain containing dtds and schemas to validate my xml files

After having your webcontainer patched, copy the dtd and xml schemas from your standard glassfish installation to the directory that you setted as your install root to the GF3 Embedded.

This directory is there that was specified while building your GF3 Embedded using the method EmbeddedFileSystem.Builder.installRoot. If you don’t use installRoot, check it using org.glassfish.api.embedded.Server.getServer(“YourServerName”).getFileSystem().installRoot. I didn’t use installRoot method, but only the EmbeddedFileSystem.Builder.instanceRoot and GF3 Embedded understand as been its instal root directory there bellow that I specified to instanceRoot method. So my dtd and schemas files must be putted into: E:\embedded-glassfish\domains\lib because I tell to instanceRoot that to use the directory E:\embedded-glassfish\domains\domain1 to put my GF Embedded temporary domain. So, finally, I just copied both directories dtds and schemas to E:\embedded-glassfish\domains\lib as seen in the picture. Remember to copy this files when creating a new install root directory to your GF3Embedded server (maybe including when creating a new gf3e domain to it).

A better way to deal with theses files

Instead a better solution is to copy the xml schemas to your own WebContainer patched project. So the patch changes to:

412         File root = _serverContext.getInstallRoot();
413         File libRoot = new File(root, "lib");
414         java.net.URL schemas = getClass().getResource("/res/lib/schemas");
415         java.net.URL dtds = getClass().getResource("/res/lib/dtds");
416 
417         //try {
418             ParserUtils.setSchemaResourcePrefix(schemas.toString() + "/");
419             ParserUtils.setDtdResourcePrefix(dtds.toString() + "/");
420             ParserUtils.setEntityResolver(habitat.getComponent(EntityResolver.class, "web"));
421         //} catch(MalformedURLException e) {
422         //    _logger.log(Level.SEVERE, "webContainer.exceptionSetSchemasDtdsLocation", e);
423         //}
424 

Now we get the dtds and schemas from our WebContainer pached jar file and then we don’t need to copy this files while building our GFServer install root.

The Second Problem

Somebody isn’t doing the job as we like… Another problem with Embedded GF lives with System.getProperty(INSTALL_ROOT_PROPERTY). As seen at com.sun.enterprise.deployment.node.SaxParserHandlerFactory it must be setted to our Embedded GF3 directory… But in my case it is setted to E:\embedded-glassfish\domains\domain1 – it’s my instance root, my domain root, not my gf3 embedded install root… it’s sad! And to ilustrate my vision this crazy world called gf embedded, see from the words of Sun’s guys:

 1     public static SaxParserHandler newInstance() {
 2         SaxParserHandler result = null;
 3         
 4         /*
 5          *If the property com.sun.aas.installRoot is defined, use the 
 6          *original implementation (SaxParserHandler) which fetches DTDs and
 7          *schemas from the installation directory tree.  Otherwise, assume that 
 8          *the app client container is running under Java Web Start. In that
 9          *case, there is no product installation directory (at least none can
10          *be assumed).  The DTDs and schemas will be retrieved from the
11          *JWS-specific jar file instead (SaxParserHandlerBundled).
12          *

13          *bnevins, Oct 16, 2008.  On Oct. 8, 2008 installRoot was changed to be setup
14          *earlier in the startup.  As a result, Embedded GF broke.  It sets up a fake installRoot, 
15          *because there is *no* install-root.
16          *Improvement: don't just see if installRoot is set -- make sure installRoot
17          *is bonafide.
18           */ 
19         
20         if(installRootIsValid()) 
21             result = new SaxParserHandler();
22         else
23             result = new SaxParserHandlerBundled();
24 
25         return result;
26     }
27     
28     private static boolean installRootIsValid() {
29         // In the context of this class, we need to make sure that we know if we 
30         //have a route to local DTDs.  Period.
31         
32         String ir = System.getProperty(INSTALL_ROOT_PROPERTY);
33         
34         if(!ok(ir))
35             return false;
36         
37         File dtds = new File(new File(ir), "lib/dtds");
38         
39         if(!dtds.isDirectory())
40             return false;
41         
42         return true;
43     }
44 
45     private static boolean ok(String ir) {
46         return ir != null && ir.length() > 0;
47     }
48 

Notes that expects to find a directory called lib/dtds into our install root directory. So, again we can put theses files or we can change the world… And, I say:
– Hey, there is a stupid hero here, call me!

So, from the first problem until now, after several hours, there are more two jobs to do:

  • Fix the system property INSTALL_ROOT_PROPERTY to the correct gf embedded install root directory and ask to another traps appears..
    But if we set it to the correct directory, the SaxParserHandlerBundled doesn’t works anymore…
  • Change my first solution copying the dtds and schemas to the /dtds and /schemas from the our jar with the patches.

Ok, let me do the both.

To do it, we must fix (break the broken code to tell truthly) the SaxParserHandlerFactory to always call SaxParserHandlerBundled. Then the solution is:

1     private static boolean ok(String ir) {
2         return "is world perfect?".equals(Boolean.TRUE.toString());
3     }
4 

And now, after changed the dtds/schemas to lives into the root of the our jar, and change the first solution to use it from this new place, I really want that it works… let me try.

Bad. I back… having bad news: org.glassfish.web.WebEntityResolver is the guy that looks for dtds and schemas into the install root directory. So, or we use the first version of our WebContainer that just append the backslash to the path of dtds and schemas to pass it to ParserUtils or, again, we change the world!

So, back again, after spending a precious time from my current project (my deadline is near dead…), I change WebContainer to use instance of a new EntityResolver capable to do the work that we expected. Also, I need to rewrite the SaxParserHandler supplied by the SaxParserHandlerFactory to work as neeeded.

A Hard Work, but now It Works

Yeah, the GF3 Embedded works without internet now! But it was too hard to fix.. and to remember, the files that I changed or created, was:

net.marciowb.web.ForABetterWorldAndSaxHandlers

   1 package net.marciowb.web;
   2 
   3 import com.sun.enterprise.deployment.node.SaxParserHandler;
   4 import com.sun.enterprise.deployment.util.DOLUtils;
   5 import java.io.InputStream;
   6 import java.util.logging.Level;
   7 import java.util.logging.Logger;
   8 import org.xml.sax.InputSource;
   9 import org.xml.sax.SAXException;
  10 
  11 /**
  12  *
  13  * @author Marcio
  14  */
  15 public class ForABetterWorldAndSaxHandlers extends SaxParserHandler {
  16 
  17     private static final Logger logger = Logger.getLogger(ForABetterWorldAndSaxHandlers.class.getName());
  18     /** prefixes for the paths to use for looking up schemas and dtds as resources */
  19     public static final String BUNDLED_SCHEMA_ROOT = "/schemas";
  20     public static final String BUNDLED_DTD_ROOT = "/dtds";
  21 
  22     /** Creates a new instance of SaxParserHandlerBundled */
  23     public ForABetterWorldAndSaxHandlers() {
  24     }
  25 
  26     /**
  27      *Returns an InputSource for the requested DTD or schema.
  28      *<p>
  29      *This implementation returns an InputSource that wraps the result
  30      *of getResourceAsStream, having
  31      *located the requested schema in the classpath.
  32      *@param the public ID of the requested entity
  33      *@param the system ID of the requested entity
  34      *@return InputSource for the requested entity; null if not available
  35      *@throws SAXException in case of errors resolving the entity
  36      */
  37     @Override
  38     public InputSource resolveEntity(String publicID, String systemID) throws SAXException {
  39         //InputSource is = (new SaxParserHandler()).resolveEntity(publicID, systemID);
  40         InputSource is = doResolveEntity(publicID, systemID);
  41         if (is == null) {
  42             logger.log(Level.WARNING, "We can't find {0} of the system {1}?", new Object[]{publicID, systemID});
  43         } else {
  44             if (is.getSystemId()==null) {
  45                 is.setSystemId(systemID);
  46             }
  47             if (is.getPublicId()==null) {
  48                 is.setPublicId(publicID);
  49             }
  50             logger.log(Level.FINE, "We found {0} of the system {1}.", new Object[]{publicID, systemID});
  51         }
  52         return is;
  53     }
  54 
  55     public InputSource doResolveEntity(String publicID, String systemID) throws SAXException {
  56         /*
  57          *This logic was patterned after that in the superclass.
  58          */
  59         try {
  60             if (publicID == null) {
  61                 // unspecified schema
  62                 if (systemID == null || systemID.lastIndexOf('/') == systemID.length()) {
  63                     return null;
  64                 }
  65 
  66                 /*
  67                  *Attempt to open a stream to the requested resource as a schema.
  68                  */
  69                 InputStream is = openSchemaStream(systemID);
  70 
  71                 if (is != null) {
  72                     /*
  73                      *We found the entity, so wrap an InputSource around the stream.
  74                      */
  75                     return new InputSource(is);
  76                 } else {
  77                     /*
  78                      *The entity was not found, so wrap an InputSource around the system ID string instead.
  79                      */
  80                     return new InputSource(systemID);
  81                 }
  82             } else {
  83                 /*
  84                  *Try to find a DTD for the entity.
  85                  */
  86                 if (getMapping().containsKey(publicID)) {
  87                     this.publicID = publicID;
  88                     InputStream is = openDTDStream(publicID);
  89                     if (is != null) {
  90                         return new InputSource(is);
  91                     }
  92                 } else if (systemID != null) {
  93                     /*
  94                      *The DTD lookup failed but a system ID was also specified.  Try
  95                      *looking up the DTD in the schemas path, because some reside
  96                      *there and were not registered in the DTD map by SaxParserHandler.
  97                      */
  98                     InputStream is = openSchemaStream(systemID);
  99                     if (is != null) {
 100                         return new InputSource(is);
 101                     }
 102 
 103                     /*
 104                      * As a last resort, try opening the DTD without going
 105                      * through the mapping table.
 106                      */
 107                     is = openStream(BUNDLED_DTD_ROOT, systemID);
 108                     if (is != null) {
 109                         return new InputSource(is);
 110                     }
 111 
 112                     is = openStream(BUNDLED_SCHEMA_ROOT, systemID);
 113                     if (is != null) {
 114                         return new InputSource(is);
 115                     }
 116                 }
 117             }
 118         } catch (Exception exc) {
 119             DOLUtils.getDefaultLogger().log(Level.SEVERE, "Error occurred", exc);
 120             throw new SAXException(exc);
 121         }
 122         return null;
 123     }
 124 
 125     /**
 126      *Returns an InputStream for the schema with the requested system ID.
 127      *@param systemID of the schema
 128      *@return an InputStream to the selected schema; null if the schema is not available
 129      */
 130     protected InputStream openSchemaStream(String systemID) {
 131         return openStream(BUNDLED_SCHEMA_ROOT, systemID);
 132     }
 133 
 134     /**
 135      *Returns an InputStream for the DTD with the requested public ID.
 136      *@param the publicID of the DTD requested
 137      *@return an InputStream to the selected DTD; null if the DTD is not available
 138      */
 139     protected InputStream openDTDStream(String publicID) {
 140         return openStream(BUNDLED_DTD_ROOT, getMapping().get(publicID));
 141     }
 142 
 143     protected InputStream openStream(final String localRoot, final String systemID) {
 144         String targetID = localRoot + "/" + systemID.substring(systemID.lastIndexOf('/') + 1);
 145         InputStream result = getClass().getResourceAsStream(targetID);
 146         if (result == null) {
 147             logger.log(Level.CONFIG, "Where is located {0} of the system {1}?", new Object[]{localRoot, systemID});
 148         } else {
 149             logger.log(Level.FINEST, "We found {0} of the system {1}.", new Object[]{localRoot, systemID});
 150         }
 151         return result;
 152     }
 153 }
 154 

net.marciowb.web.WebEntityResolverHowToRegisterIt

   1 /*
   2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
   3  *
   4  * Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved.
   5  *
   6  * The contents of this file are subject to the terms of either the GNU
   7  * General Public License Version 2 only ("GPL") or the Common Development
   8  * and Distribution License("CDDL") (collectively, the "License").  You
   9  * may not use this file except in compliance with the License. You can obtain
  10  * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
  11  * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
  12  * language governing permissions and limitations under the License.
  13  *
  14  * When distributing the software, include this License Header Notice in each
  15  * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
  16  * Sun designates this particular file as subject to the "Classpath" exception
  17  * as provided by Sun in the GPL Version 2 section of the License file that
  18  * accompanied this code.  If applicable, add the following below the License
  19  * Header, with the fields enclosed by brackets [] replaced by your own
  20  * identifying information: "Portions Copyrighted [year]
  21  * [name of copyright owner]"
  22  *
  23  * Contributor(s):
  24  *
  25  * If you wish your version of this file to be governed by only the CDDL or
  26  * only the GPL Version 2, indicate your decision by adding "[Contributor]
  27  * elects to include this software in this distribution under the [CDDL or GPL
  28  * Version 2] license."  If you don't indicate a single choice of license, a
  29  * recipient has the option to distribute your version of this file under
  30  * either the CDDL, the GPL Version 2 or to extend the choice of license to
  31  * its licensees as provided above.  However, if you add GPL Version 2 code
  32  * and therefore, elected the GPL Version 2 license, then the option applies
  33  * only if the new code is made subject to such option by the copyright
  34  * holder.
  35  *
  36  */
  37 package net.marciowb.web;
  38 
  39 import java.util.logging.Level;
  40 import java.util.logging.Logger;
  41 import org.xml.sax.EntityResolver;
  42 import org.xml.sax.InputSource;
  43 import org.xml.sax.SAXException;
  44 
  45 import org.jvnet.hk2.annotations.Service;
  46 import org.jvnet.hk2.annotations.ContractProvided;
  47 import org.jvnet.hk2.component.PostConstruct;
  48 import org.apache.catalina.startup.Constants;
  49 
  50 import java.io.IOException;
  51 import java.util.Map;
  52 
  53 import com.sun.enterprise.util.MapBuilder;
  54 
  55 /**
  56  * {@link EntityResolver} that recognizes known public IDs of JavaEE DTDs/schemas
  57  * and return a local copy.
  58  *
  59  * <p>
  60  * This implementation assumes that those files are available in
  61  * <tt>/schemas</tt> and <tt>/dtds</tt> of this Jar package (same classloader of
  62  * this class). But in different environment, different implementation can be plugged in
  63  * to perform entirely different resolution.
  64  *
  65  * How to register this service?
  66  *
  67  * @author Marcio Wesley Borges
  68  */
  69 @Service(name = "web")
  70 @ContractProvided(EntityResolver.class)
  71 public class WebEntityResolverHowToRegisterIt implements EntityResolver, PostConstruct {
  72     //@Inject
  73     //ServerContext serverContext;
  74 
  75     /**
  76      * Known DTDs.
  77      *
  78      * Expose the map so that interested party can introspect the table value and modify them.
  79      */
  80     public final Map<String/*public id*/, String/*bare file name*/> knownDTDs = new MapBuilder<String, String>().put(Constants.TldDtdPublicId_11, "web-jsptaglibrary_1_1.dtd").put(Constants.TldDtdPublicId_12, "web-jsptaglibrary_1_2.dtd").put(Constants.WebDtdPublicId_22, "web-app_2_2.dtd").put(Constants.WebDtdPublicId_23, "web-app_2_3.dtd").build();
  81 
  82     @Override
  83     public void postConstruct() {
  84         //File root = serverContext.getInstallRoot();
  85         //File libRoot = new File(root, "lib");
  86     }
  87 
  88     /**
  89      * If the parser hits one of the well-known DTDs, parse local copies instead of hitting
  90      * the remote server.
  91      */
  92     @Override
  93     public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
  94         InputSource is = doResolveEntity(publicId, systemId);
  95         if (is == null) {
  96             Logger.getLogger(WebEntityResolverHowToRegisterIt.class.getName()).log(Level.CONFIG, "Where is located entity {0} of the system {1}?", new Object[]{publicId, systemId});
  97         }
  98 
  99         return is;
 100     }
 101 
 102     private InputSource doResolveEntity(String publicId, String systemId) throws SAXException, IOException {
 103         String fileName = knownDTDs.get(publicId);
 104         if (fileName != null) {
 105             java.io.InputStream stream = getClass().getResourceAsStream("/dtds/" +fileName);
 106             if (stream == null) {
 107                 return null;
 108             }
 109             return new InputSource(stream);
 110         }
 111 
 112         return null;
 113     }
 114 }
 115 

com.sun.enterprise.deployment.node.SaxParserHandlerFactory

   1 /*
   2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
   3  *
   4  * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
   5  *
   6  * The contents of this file are subject to the terms of either the GNU
   7  * General Public License Version 2 only ("GPL") or the Common Development
   8  * and Distribution License("CDDL") (collectively, the "License").  You
   9  * may not use this file except in compliance with the License. You can obtain
  10  * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
  11  * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
  12  * language governing permissions and limitations under the License.
  13  *
  14  * When distributing the software, include this License Header Notice in each
  15  * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
  16  * Sun designates this particular file as subject to the "Classpath" exception
  17  * as provided by Sun in the GPL Version 2 section of the License file that
  18  * accompanied this code.  If applicable, add the following below the License
  19  * Header, with the fields enclosed by brackets [] replaced by your own
  20  * identifying information: "Portions Copyrighted [year]
  21  * [name of copyright owner]"
  22  *
  23  * Contributor(s):
  24  *
  25  * If you wish your version of this file to be governed by only the CDDL or
  26  * only the GPL Version 2, indicate your decision by adding "[Contributor]
  27  * elects to include this software in this distribution under the [CDDL or GPL
  28  * Version 2] license."  If you don't indicate a single choice of license, a
  29  * recipient has the option to distribute your version of this file under
  30  * either the CDDL, the GPL Version 2 or to extend the choice of license to
  31  * its licensees as provided above.  However, if you add GPL Version 2 code
  32  * and therefore, elected the GPL Version 2 license, then the option applies
  33  * only if the new code is made subject to such option by the copyright
  34  * holder.
  35  */
  36 
  37 package com.sun.enterprise.deployment.node;
  38 
  39 import net.marciowb.web.ForABetterWorldAndSaxHandlers;
  40 import static com.sun.enterprise.util.SystemPropertyConstants.INSTALL_ROOT_PROPERTY;
  41 
  42 import java.io.File;
  43 
  44 /**
  45  *Provides the appropriate implementation depending on the current
  46  *runtime environment.
  47  *
  48  * @author tjquinn
  49  */
  50 public class SaxParserHandlerFactory {
  51 
  52     /** Creates a new instance of SaxParserHandlerFactory */
  53     public SaxParserHandlerFactory() {
  54     }
  55 
  56     public static SaxParserHandler newInstance() {
  57         SaxParserHandler result = null;
  58 
  59         /*
  60          *If the property com.sun.aas.installRoot is defined, use the
  61          *original implementation (SaxParserHandler) which fetches DTDs and
  62          *schemas from the installation directory tree.  Otherwise, assume that
  63          *the app client container is running under Java Web Start. In that
  64          *case, there is no product installation directory (at least none can
  65          *be assumed).  The DTDs and schemas will be retrieved from the
  66          *JWS-specific jar file instead (SaxParserHandlerBundled).
  67          *
  68          *bnevins, Oct 16, 2008.  On Oct. 8, 2008 installRoot was changed to be setup
  69          *earlier in the startup.  As a result, Embedded GF broke.  It sets up a fake installRoot,
  70          *because there is *no* install-root.
  71          *Improvement: don't just see if installRoot is set -- make sure installRoot
  72          *is bonafide.
  73           */
  74 
  75         if(installRootIsValid())
  76             result = new SaxParserHandler();
  77         else
  78             result = new ForABetterWorldAndSaxHandlers();
  79 
  80         return result;
  81     }
  82 
  83     private static boolean installRootIsValid() {
  84         // In the context of this class, we need to make sure that we know if we
  85         //have a route to local DTDs.  Period.
  86 
  87         String ir = System.getProperty(INSTALL_ROOT_PROPERTY);
  88 
  89         if(!ok(ir))
  90             return false;
  91 
  92         File dtds = new File(new File(ir), "lib/dtds");
  93 
  94         if(!dtds.isDirectory())
  95             return false;
  96 
  97         return true;
  98     }
  99 
 100     private static boolean ok(String ir) {
 101         return "is the world a perfect place?".equals(Boolean.TRUE.toString());
 102     }
 103 
 104 }
 105 

com.sun.enterprise.web.WebContainer

   1         // TODO: ParserUtils should become a @Service and it should initialize itself.
   2         // TODO: there should be only one EntityResolver for both DigesterFactory
   3         // and ParserUtils
   4         File root = _serverContext.getInstallRoot();
   5         
   6         java.net.URL schemas = getClass().getResource("/schemas");
   7         java.net.URL dtds = getClass().getResource("/dtds");
   8 
   9         //try {            
  10             ParserUtils.setSchemaResourcePrefix(schemas.toString() + "/");
  11             ParserUtils.setDtdResourcePrefix(dtds.toString() + "/");
  12             ParserUtils.setEntityResolver(new WebEntityResolverHowToRegisterIt());
  13         //} catch(MalformedURLException e) {
  14         //    _logger.log(Level.SEVERE, "webContainer.exceptionSetSchemasDtdsLocation", e);
  15         //}
  16 
  17         instanceName = _serverContext.getInstanceName();
  18 

The Patch

If you prefer, I put the download bellow to simplifier your work:

It’s all needed.

I will go back to my work for now. Bye.

Ps.: Oracle, feel free to pay me for the time that I expended to fix it. I lost more than 15 precious hours of work devoted to resolve this.

Posted in development, it, java, tips, web | 2 Comments

Amplificando o Sinal Emitido pela Cinta de Monitoramento

Capitação do Sinal Transmitido pela Cinta de Monitoramento Cardíaco

BOBINA AO REDOR DO TRANSMISSOR

Como mencionado anteriormente, na versão 1.0 do Gym4Us, utilizo uma bobina para detectar o campo magnético oscilante que é criado pelo transmissor da cinta de monitoramento quando de uma batida do coração. A bobina deve estar o mais próxima possível do transmissor da cinta. Da forma como fiz, optei por construir a bobina em volta do próprio transmissor da cinta e para manter os fios presos, usei uma fita adesiva. Use um fio esmaltado e dê vinte ou trinta voltas ao redor da parte onde fica o transmissor da cinta. Lembre-se que as duas extremidades do fio deverão ser ligadas a um par de fio encapado e flexível.

Para saber se está funcionando, é necessário usar a cinta como se faria normalmente. Já tendo vestido a cinta, use um oscilóscopio para monitorar o sinal. Ajuste o nível do oscilóscopio para 100mV e o tempo para algo entre 0.1ms a 1ms. Como abaixo:

REGULAGEM DO OSCILÓSCOPIO e visualização do sinal transmitido pela cinta

Com a cinta que estou usando, obtenho um sinal de até 500mVpp. A frequência da sequência de pulsos é de aproximadamente 5.4KHz e sua duração é de aproximadamente 5ms a 7ms. Veja:

PROPRIEDADES DO SINAL - tensão, frequência e duração

Fiz testes com uma bobina externa a cinta, ou seja, que havia sido enrolada em um eixo externo, mas que ficava próxima ao transmissor, e notei que a intensidade do sinal era de no máximo 30mV. Ainda assim, isso é suficiente para excitar o amplificador que descrevo a seguir.

BOBINA EXTERNA À CINTA

Amplificando o Sinal Emitido pela Cinta de Monitoramento

The signal amplifier board

Mesmo enrolando a bobina diretamente ao redor do transmissor da cinta, ainda assim o sinal detectado será muito fraco para qualquer aplicação direta. Seja, para acender um led, ou ser processado por um microcontrolador, ou mesmo, dependendo de seu multímetro, até para ser visualizado sem a ajuda de um oscilóscopio. O sinal deve ser amplificado para que tenha utilidade.

Para amplificar o sinal, utilizei um simples amplificador operacional de baixa voltagem, o LM358, usando um único estágio. A tensão enduzida na bobina- cerca de 30mVpp a 500mVpp – é amplificada para 5V – mesma tensão de alimentação do PIC. Provavelmente deve ter como fazer a amplificação utilizando uns poucos transistores, talvez até um único transistor.

No entanto, foi esse circuito abaixo que usei no Gym4Us 1.0. Se precisar regular o amplificador para um sinal inferior a 50mVpp, diminua o valor de R3. Lembro que a bobina L1 não tem um valor especifico e pode-se desconsiderar o valor dela descrito no circuito. Eu mesmo enrolei umas vinte ou trinta voltas de fio esmaltado ao redor do transmissor da cinta e isto bastou.

Signal amplifier module of the Gym4Us 1.0 box

Este circuito tem a característica de transformar a onda senoidal de entrada para uma onda quadrada em sua saída. O sinal de entrada cuja intensidade varia entre 30mVpp a 500mVpp é amplificado para uma amplitude de 4000V~5000mVpp quando em nível alto. Para se ter uma ideia de como amplificador atua sobre o sinal, veja as figuras abaixo.

The channel #1 has the signal (a sine wave) catched by the coil before any amplification. The channel #2 shows the signal after amplification. Also, the amplifier modify the signal to a square wave.
The signal received from the HRM belt transmiter is indicated at channel #1 - it has 500mVpp of amplitude and its wave is a sine. The channel #2 shows the signal after the amplification. The signal is amplificated to 4~5Vpp and its wave is changed to square.

Depois deste processo de amplificação e adequação do formato de onda, o sinal está pronto para ser levado ao microcontrolador.

Posted in development, electronic, gadgets, gym4us, projects | 3 Comments

Circuito: O Hardware do Gym4Us 1.0

O hardware do Gym4Us 1.0 é bastante simples e carece de otimizações. Não o simplifiquei por questões de tempo e dinheiro – queria economizar ambos. A versão que estou apresentando é a primeira que fiz e até o momento em que escrevo este artigo, é também a única. Sendo assim, enquanto montava o circuito, também tinha em mente minimizar problemas e por isso modularizei todo o circuito de modo que pudesse testar cada módulo individualmente. Se algum módulo apresentasse problemas, poderia substituí-lo. A idéia era isolar qualquer problema que ocorrece de modo a não impactar em todo o circuito. Esta ideia de simplificar e modulalizar tudo, se aplica desde o design do circuito, até a montagem.

Como verão, o circuito pode ser tremendamente melhorada e isso para praticamente todos os aspectos, incluindo: gestão de consumo de energia, tamanho total do circuito, conexões externas, design de caixa para acondionamento do circuito, preço dos componentes, re-design dos módulos etc. Já tenho vários planos para as próximas versões e isso inclui:

  • Uso de um módulo Bluetooth comercial
    No circuito que montei acabei utilizando um módulo Bluetooth de um GPS Bluetooth velho
  • Uso do módulo receptor Polar RMCM01
    Infelizmente, como não consegui montar um receptor adequado, fui obrigado a enrolar uma próximo do transmissor da cinta. Pelo fato do RMCM01 ser pequeno e relativamente barato, creio ser preferível usá-lo do que tentar criar um receptor equivalente. O maior problema que tive para construir um receptor para as cintas de 5KHz é que, onde moro, aqui em Brasília-DF, não há amplificadores operacionais com as características necessárias para a amplificação e filtragem do sinal.
  • Substituição do PIC16F628A
    Eu acabei por usar o PIC16F628A, pois tenho uma centena destes sem uso aqui em meu laboratório. No entanto, é desejável substituí-lo por algum outro microcontrolador de baixo consumo e de poucos requisitos para o circuito de apoio. Talvez algum PIC18 ou mais provavelmente, algum ARM7. Quando já estava testando a firmware para o PIC16F628A, me dei conta que ele necessitava de 5V para que funcionasse adequadamente com um clock de 20MHz. Da forma como idealizei o circuito, para que a amostragem do sinal ocorra, é necessário um clock de 20MHz para este PIC em particular. Dessa forma, acabei, sem me dar conta, o requisito de alimentação para o circuito, já que teria que suprir 5V para o PIC.
  • Usar algum gerenciador de carga de bateria e alimentação
    Estou utilizando a bateria e o circuito gereciador do GPS Bluetooth que desmontei. O ideal é usar alguma solução que seja aplicável a qualquer um que for montar o circuito.
  • Eliminar o circuito de Step-Up e regulagem para 5V
    Como expliquei antes, por causa do PIC16F628A estar funcionando a 20MHz, é necessário alimentá-lo com 5V. Quando ele for substituído por algum microcontrolador de 3,3V, ou inferior, creio que ficará mais fácil e econômica a alimentação do circuito. Ainda assim, pode ser que seja necessário manter o circuito de Step-Up ou algum equivalente, e ainda manter alguma regulagem de voltagem para o microcontrolador, mas provavelmente isto será mais fácil do que fazendo da forma como está atualmente. De qualquer forma, mesmo que continuasse necessário produzir os 5V, isto poderia ser realizado com algum IC comercial e de preferência que fizesse o Step-Up e a regulagem tudo num único circuito. Há vários ICs com esse propósito que podem ser utilizados. Na solução que eu fiz para o Gym4Us 1.0, acabei por criar três circuitos que juntos forma o step-up e a regulagem que provê os 5V. Obviamente isto pode e deve ser simplificado!
  • Adicionar suporte a cartão de memória
    A ideia é gravar o percurso usando um GPS e juntamente gravar o monitoramento do coração. Tenho visto que o NST não é tão estável quanto gostaria e por isso é interessante manter o tracklog e o HRM gravado por hardware para recuperação em caso de pane do NST.
  • ECG
    Criar um hardware para realizar o ECG e enviar esses dados no mesmo pacote do NST. Outras aplicações poderão mostrar diretamente o ECG, sua forma de onda, batimento por minuto e até prever arritimias, bem como, outras anormalidades. Isto também substituirá, com precisão, a cinta de monitoramento cardíaco de 5KHz.
  • Sensor de temperatura
  • Sensor de umidade
  • Sensor de nível de CO2
  • Conta giros para a bike
  • Sensor de cadência para a bike
  • Display
    Dependendo do uso que se destinatar, um display pode ser interessante. Hoje há muitos displays gráficos de celulares que são baratos e eficientes.
  • Utilização de energia solar para carga de bateria
  • Transmissão dos dados também por outros meios, como:
    • RS-232
    • Infravermelho
    • Ultra-som
    • Zigbee
    • Wifi

Uma das vantagens no frame de dados que é enviado ao NST, é que, ao que observei, ele é redimensionável. Dessa forma é possível aumentar seu tamanho e enviar outros dados que somente aqueles usados pelo NST.

O circuito do Gym4Us 1.0 é composto dos seguintes módulos:

  • Circuito de Recepção e Amplificação de Sinal emitido pela cinta transmissora;
  • Módulo de Microcontrole para amostragem de sinal, detecção de sinal, cálculo de batimento do coração e empacotamento dos dados no formato do Nokia Sports Tracker para transmissão via Bluetooth;
  • Módulo Bluetooth para transmissão dos dados para o celular;
  • Módulo de Energia
    Com base numa bateria recarregável, um gerenciador de recarga e circuito para step-up e regulagem, temos a energia necessária para o módulo bluetooth, o microcontrolador e o módulo de recepção do sinal emitido pela cinta transmissora. No circuito que fiz, acabei por criar um módulo de step-up e regulagem para 5V. Dessa forma, o módulo de energia final, ficou dividido em:

    • Partes obtidas do GPS Bluetooth que utilizei
      • Bateria recarregável de íons de lítio;
      • Módulo de gerenciamento e recarga da bateria de íons de lítio;
      • Módulo de alimentação para o módulo Bluetooth (3,3V);
    • Circuitos que criei para a alimentação do Gym4Us 1.0
      – composto de um step-up e regulador de voltagem

      • Módulo oscilador
        Baseado num 555 gera um sinal quadrado para o circuito de step-up
      • Módulo de chaveamento, indução de sobre-voltagem e retificação
        Baseado num transistor de potência BD139, um indutor de 3.3mH para elevação de voltagem, diodo schottky para retificação e acumuladores. Um transistor de uso geral e um diodo comum também funcionariam. O valor do inductor não é crítico e indutores entre 20uH a 30mH podem ser usados, desde que a frequência do oscilador seja ajustada. Utilize um multímetro para ajustar o oscilador de modo a obter a maior tensão possível e assim, o melhor rendimento.
        Junto com o módulo oscilador, compõe o circuito de step-up.
      • Módulo regulador de voltagem
        Baseado num 7805
Posted in development, electronic, gadgets, gym4us, it, projects, tips | Leave a comment

Gym4Us 1.0 – O Que Você Precisa Para Fazê-lo

Se quer já começar a correr atrás dos componentes necessários para o projeto, segue uma lista parcial:

  • Osciloscópio
    Pode ser qualquer.
    Eu mesmo uso um equipamento de baixo custo que funciona via USB. Trata-se do DSO-2150 USB que me custou USD$ 150,00 no eBay.
  • Multímetro
    Novamente pode ser qualquer aparelho – até mesmo aqueles de R$ 15,00 que se encontra nos camelôs.
  • Cinta transmissora de monitoramento cardíaco compatível com o padrão de 5KHz ou 5.4KHz
    Eu uso a Polar T31 non-coded
  • Opcional: Relógio ou equipamento receptor para a cinta transmissora de modo que você possa conferir a recepção e transmissão
    Estou sem relógio para a cinta Polar T31. Inicialmente tinha a cinta e o relógio do Polar RS300X, mas foi vendido e não tenho mais.
    O que sobrou foi um sistema de monitoramento Timex com o relógio e a cinta, mas a cinta é incompatível com o Gym4Us, pois transmite seus dados num sistema RF digital que é totalmente diferente do que foi utilizado no projeto do Gym4Us 1.0. Dessa forma, só utilzei o Timex para conferir se o batimento cardíaco que calculei estava correto (e está!). Obviamente usei as duas cintas simultâneamente para poder conferir o resultado. Mas se você já tiver o relógio para sua cinta, com certeza será melhor até mesmo para o uso prático/diário, pois poderá olhar no relógio quando precisar, sem ter que ficar dependendo unicamente do celular para conferir seus batimentos.
  • Módulo Bluetooth para Perfil de Porta Serial com UART
    Eu desativei o módulo GPS de um GPS Bluetooth e estou usando somente o módulo Bluetooth dele para transmitir os dados
  • Opcional: Dongle USB Bluetooth
    Se seu desktop ou notebook já tiver suporte a Bluetooth, não é necessário. Qualquer adaptador serve. De qualquer forma, é opcional se você não for depurar pelo PC. Se você for comprar um adaptador Bluetooth, compre algum que tenha o gerenciado da BlueSoleil ou Widcomm/Broadcomm, pois o gerenciador do Windows é quase inútil!
    No meu caso foi extremamente útil no desenvolvimento do Gym4Us, desde a fase de testes, até a fase de descoberta do protocolo aceito pelo Nokia Sports Tracker.
  • Microcontrolador PIC16F628A
    Futuramente irei usar outro microcontrolador. Provavelmente um ARM7 da família LPC2000 da NXP que consome pouca energia, é barato e pequeno.
  • Programador para o PIC16F628A
    Eu tenho um ICD2 da Labtools. Você pode até mesmo fazer o seu, há vários projetos na Internet que ensinam a montar programadores para PIC. No Mercado Livre também pode-se encontrar soluções prontas de baixo custo.

Reaproveitando Componentes de Sucata para o Projeto Gym4Us

Durante toda a pesquisa e desenvolvimento do que me levou ao Gym4Us, tive em mente minha atual falta de grana e por isso evitei ao máximo adquirir qualquer componente eletrônico. Me limitei aos que já possuo em meu laboratório – que também é meu escritório e quarto de visitas. Tenho 99.9% de coisas entulhadas em meu laboratório que nunca utilizo e dessa vez quis dar uso a algumas delas.

Do Monitor CRT: Fios Esmaltados

Como exemplo, cito o monitor CRT que não funcionava mais e que já estava sem uso há mais de um ano e que o desmontei para retirar os fios esmaltados que necessitava. Preferi retirar os fios do monitor, do que de transformadores velhos que tenho de montes – dá muito trabalho retirar estes fios de transformadores velhos e corre-se sempre o risco de machucar-se com as placas de ferro de seu núcleo e carcaça; eu sempre acabo por me cortar quando tento retirá-las.

Do MODEM: Indutores

Além do monitor e dos transformadores, também ao longo de duas décadas, acumulei uma série de outros aparelhos que já não funcionam mais, mas que acabo guardando para que justamente possa reaproveitar seus componentes e assim, mesmo a custa de tempo, tentar economizar algum dinheiro.

Tinha alguns MODEMs e switches velhos e vi que eles tinham indutores que poderiam ser úteis posteriormente no projeto.

Do Rádio AM/FM: Bobina com Núcleo de Ferrite para Ondas Curtas

Do rádio velho que não funcionava, tirei a bobina com núcleo de ferrite. Ela poderia ser útil no caso de eu desenvolver uma bobina para a captação a distância (uns 30cm a 1m) do campo magnético oscilatório gerado pelo transmissor da cinta de monitoramento cardíaco.

GPS Bluetooth: Energia e Módulo Bluetooth

Há alguns anos atrás, por meados de 2006/2007, ao notar que naquela época havia um nicho – que já não há mais – de mercado, comecei a vender GPSs Bluetooth. Naquela época a qualidade dos equipamentos era outra, bem como as marcas que comercializava e o preço era coisa importante em tempos de dólar alto e tecnologia nova. Dessa forma, naquela época, optei por equipamentos que fossem mais baratos para que pudesse vendê-los – diferentemente de hoje que a marca dos equipamentos que vendo são de qualidade e os produtos também o são e apesar do preço ser superior, a qualidade também o é e não tenho problema. Como resultado da tecnologia ser nova e o equipamento ser de baixo custo, acabei por receber um ou outro equipamento com defeito. Ao longo do tempo, conforme ia identificado os equipamentos defeituosos, após providenciar outro equipamento ao cliente ou a devolução do valor pago, os separava de modo a devolvé-los ao fabricante numa futura oportunidade. A questão é que devolver equipamentos que não são fabricados no Brasil para o fabricante no exterior, é coisa complicada, cara e burocrática. Dessa forma, tive que assumir o prejuízo e ficar com as unidades defeituosas. Não as joguei fora, simplesmente anotei o problema e as deixei separadas para que pudesse usá-los como sucata. Devo ter uma dúzia de GPS Bluetooth com problemas.

A cinta de monitoramento utilizada pelo Nokia Sports Tracker transmite seus dados via Bluetooth e logo de início imaginei que o protocolo utilizado era o SPP (SPP – Serial Port Profile). Para ter certeza, fiz alguns testes utilizando um adaptador Bluetooth plugado ao meu computador e realizei o pareamento com o Nokia Sports Tracker. O NST buscava justamente pelo serviço de porta serial. Após eu indicar ao NST que ele deveria se conectar ao computador, ele o fazia conectando-se ao serviço de SPP que havia habilitado no computador. O gerenciador WidComm me indicava a conexão e o NST ficava aguardando dados do dispositivo Bluetooth, no caso, o computador. Dessa forma, tive certeza que realmente era necessário um módulo Bluetooth com o perfil de porta serial.

Como sabia que iria precisar de um módulo bluetooth para poder transmitir a informação do batimento cardíaco ao celular, logo após desconsiderar o gasto de dinheiro com aquisição de um módulo bluetooth no exterior, acabei por me lembrar deste monte de GPS Bluetooth com problema. Para quem não sabe, os GPS Bluetooth utilizam o perfil de porta serial. O próprio protocolo NMEA 0183 foi criado pensando-se numa comunicação serial e geralmente os GPS Bluetooth trabalham com o protocolo NMEA 0183. Dessa forma, tem-se um módulo Bluetooth com suporte ao perfil de porta serial em cada um dos GPS Bluetooth. Pensei que a chance conseguir reaproveitar algum módulo bluetooth dos GPS Bluetooth seria pequena, mas mesmo assim, devia tentar.

Como justamente iria usar um GPS Bluetooth com problemas, tinha que ter certeza que o problema não estaria na parte bluetooth, ou do contrário, não me teria utilidade. Comecei escolhendo algum GPS Bluetooth que eu tivesse certeza que o bluetooth funcionava. Para tanto, liguei o GPS Bluetooth e realizei o pareamento do GPS Bluetooth com o computador e capturei as sentenças NMEA utilizando um programa de comunicação serial. Se você não tem experiência com dispositivos seriais com os GPS Bluetooth, saiba que qualquer programa de comunicação serial serve. Incluindo: Hyperterminal, Putty, screen, gtkterm e outros. Então usando o programa de comunicação serial, vi que as sentenças estavam sendo recebidas corretamente. O problema do GPS Bluetooth parecia estar no próprio módulo GPS que nunca conseguia estabelecer sua posição, mesmo quando em condições de recepção excelentes. Ótimo! Consegui um módulo Bluetooth.

Identificado um aparelho que tivesse o módulo Bluetooth funcionando, após abri-lo, podia-se ver claramente o que era o módulo GPS e o que era o módulo Bluetooth. O próximo trabalho seria identificar qual o pino onde se encontra a saída UART (TX – saída da porta serial) do módulo GPS. O conceito é simples: o GPS envia os dados de suas sentenças utilizando-se de uma saída serial; esta saída serial deve estar ligada a uma porta serial de entrada (RX – recepção) da UART do módulo Bluetooth. Assim, o GPS transmite os dados das sentenças para o módulo Bluetooth e o módulo Bluetooth as recebe. Então bastava-se encontrar o pino de saída do GPS ou o pino de recepção do módulo Bluetooth. Para isso, sabia previamente de algumas características do sinal que deveria identificar, sendo:

  • A comunicação típica de qualquer GPS é 4800 bauds, nenhuma paridade, um bit de parada e sem controle de fluxo.
    As vezes o baudrate é outro, mas isto é incomum. Tenho visto módulos GPS com baud rates de 4800, 9600 e 38400.
  • O nível do sinal da UART seria LVTTL. No caso, poderia estar entre 1,8V a 3,6V quando em nível alto e abaixo de 0,6V quando em nível baixo;
  • As sentenças NMEA são enviadas todas de uma vez e após serem enviadas, há uma pequena pausa na transmissão do GPS, ou seja, após uma atividade, há um pequno período onde não há atividade alguma na UART do GPS. Este padrão se repete a cada segundo.
  • Por fim, tendo sorte, haveria um pino do lado do módulo GPS que teria um pino com os mesmos sinais do lado do módulo Bluetooth, ou seja, um pino com a TX do GPS e do lado do Bluetooth, haveria uma RX.

Com o GPS Bluetooth ligado e transmitindo para o bluetooth do computador (se não estiver transmitindo, geralmente o GPS é desligado para economizar energia e seria impossível identificar atividade), após inspecionar, com ajuda de um multímetro (não cheguei a usar o oscilóscopio até aqui) vários pinos do lado do GPS e do lado Bluetooth, encontrei um padrão de sinal que parecia ser o característico de uma UART transmitindo dados típicos de GPS (sentenças NMEA). Para ter certeza, aí então, usei o oscilóscopio para “ver” se realmente o sinal estava dentro do que seria uma UART. Pelo oscilóscopio, mesmo sem fazer uma análise dos dados à nível digital, vi que realmente era uma UART e que a transmissão estava se dando no que parecia ser 4800 bauds.

Identificado os pinos de transmissão do GPS e recepção do Bluetooth, o próximo passo foi desativar o pino de transmissão do GPS. Isto deu um pouquinho de trabalho pois se trata de componentes muito pequenos e com alta densidade de integração. Tentei simplesmente queimar com o ferro de solda o pino TX do módulo GPS, mas ainda assim, mesmo sem o pino RX do Bluetooth apresentar atividade aparente, todas as vezes que ligava o GPS Bluetooth, era transmitido alguns dados contendo a identificação da firmware do módulo GPS e após isso, nada mais era transmitido – naturalmente via isso com o programação de comunicação serial no computador. Então, presumi que talvez houvesse alguma outra saída que ainda estivesse se comunicando com o módulo Bluetooth, derrepente alguma saída de depuração ou mesmo algum outro tipo de saída, como: SPI ou I2C. Num ato de loucura, resolvi ir queimando pino-a-pino do módulo GPS até que nada fosse apresentado no programa de comunicação serial que usava no computador ao ligar o GPS Bluetooth. Após queimar alguns pinos, como eram muitos, já estava um tanto entediado com aquilo. Então, resolvi mesmo foi tentar remover na base da força bruta o módulo GPS do circuito. Peguei um alicate e tentei remover o módulo GPS, sem sucesso – estava muito bem preso ao resto do circuito. Insanamente, com o próprio alicate, comecei a “raspar” aquele monte de capacitores e resistores SMD em miniatura que haviam no módulo GPS (e somente no módulo GPS). Após isso, vi que ao ligar o GPS Bluetooth, nada mais era transmitido ao computador, apesar do Bluetooth ainda estar funcionando. Restava então saber, se seria capaz de transmitir dados para o módulo Bluetooth usando daquele pino de recepção que havia identificado previamente.

A continuar…. (estou escrevendo)

Posted in development, electronic, gadgets, gym4us, it, projects, tips | 3 Comments

Detectando o Batimento do Coração

Na busca de uma solução, tive antes que entender o funcionamento de uma série de coisas, inclusive como funcionam as cintas de monitoramento cardíaco, seus sistemas de captação, amplificação, filtragem, detecção, codificação e transmissão. Além disso, também estudei um pouco sobre o ECG e as diferentes maneiras de se perceber o batimento do coração. Não sou médico, nem engenheiro, nem cientista; sou um mero programador que gosta de xeretar, portanto, de antemão, peço desculpas pelas tolices que falei e falarei até o fim deste artigo e de futuros artigos correlatos. A seguir, irei expor o que entendi e como isso me direcionou ao longo da pesquisa e desenvolvimento da solução final. Somente ao fim de todos os artigos irei compilar e acrescentar as informações necessárias para se compor o projeto em si, de forma simples, direta e técnica do ponto de vista do hardware e software; antes disso, haverá muito blah-blah-blahh de minha parte.

Cintas de Monitoramento Cardíaco

O funcionamento das cintas de monitoramento cardíaco, pode ser descrito nos passos abaixo:

  1. As cintas de monitoramento cardíaco captam a diferença de potencial (voltagem) que ocorre no momento da contração/relaxamento das células cardíacas como um todo.
  2. Posteriormente o sinal captado é amplificado e filtrado. Este processo pode-se repetir várias vezes, até que o sinal possa ser utilizado no próximo estágio do circuito.
  3. O sinal amplificado pode ser aplicado num microcontrolador ou mesmo enviado para algum circuito analógico de detecção de pico, por exemplo.
  4. Dependendo da cinta de monitoramento, a informação sobre o batimento cardíaco poderá ser transmitida para algum monitor (relógio, computador, smartphone ou algum outro equipamento) utilizando-se uma das abordagens abaixo:
    • A cada batimento do coração um sinal é transmitido pela cinta de monitoramento para o equipamento receptor que fará a outra parte do monitoramento. Atualmente, vi que esta abordagem é a mais comum, ou seja, o coração bate, a cinta percebe isso e para notificar o equipamento receptor, envia um sinal que é interpretado, como: “o coração bateu”.
    • Outra abordagem é a cinta transmissora perceber e acumular dois ou mais batimentos do coração, de modo que ela calcule a taxa de batimento do coração e já transmita essa informação para o receptor, como: “o coração está batendo a uma taxa de X batidas por minuto”. Nesse caso, pode-se postergar o envio dessa informação de modo a enviá-la de tempos-em-tempos (caso do sistema Timex que envia de dois-em-dois segundos a taxa de batimento por minuto).

As vezes a cinta irá realizar a transmissão por algum processo que evite ou minimize a interferência com outros equipamentos, incluindo outras cintas de mesmo modelo. Há várias formas disso poder ser realizado e não irei listá-las aqui.

Sistemas de Transmissão

A cinta pode transmitir a informação do batimento cardíaco para o equipamento monitor utilizando-se diferentes meios físicos e lógicos, sendo que particularmente dois sistemas é de nosso interesse:

RF – radiofrequência, ou seja, ondas eletromagnéticas

Há diversas frequências que podem ser utilizadas e algumas vezes o sinal poderá ser ou não modulado, depende do modelo da cinta. Quando estiver modulado, algumas vezes a transmissão poderá ser digital. Sendo alguns deles:

  1. Modulação FM para envio de dados digitais (não sei a faixa de frequência)
    Caso do Timex
  2. Bluetooth (2.4GHz)
    Usado pela cinta Polar for Nokia, BioHarness da Zephyr, Spurty Chest Strap e FRWD B, por exemplo.
  3. ANT+ (2.4GHz)
    Usado pela Garmin e Timex, dentre outras.
  4. Demais sistemas de transmissão de alta-frequência
    Caso dos Garmin, alguns da Polar e outros muitas vezes utilizando a faixa de 2.4GHz, mas usando protocolos proprietários de cada fabricante e modelo de equipamento.

Vale ressaltar que a lista acima não tende a esgotar os sistemas RF em uso por monitores cardíacos, mas somente exemplificar o que é na verdade uma gigante variedade possível. E além da frequência de 2.4GHz, que é bastante utilizada por ser uma faixa para uso livre internacionalmente, outras faixas de frequências também são utilizadas.

O sistema de recepção para cintas que transmitam via rádiofrequência, são os convencionais para RF, mas variando desde simples receptores FM a sofisticados mecanismos de divisão de frequência. Ressaltando que posteriormente ao receptor RF, se encontra etapas decodificadoras características de cada solução.

Modulação de campo magnético

Este meio de transmissão cria um campo magnético e o modula de modo a transmitir a informação da cinta para o equipamento monitor. Com certeza, é a forma mais popular e ainda em uso existente, apesar de estar sendo substituída pela transmissão em RF, principalmente dados os atuais avanços dos últimos anos em relação a transmissão de dados via RF utilizando-se pouca energia.

Os equipamentos que utilizam a modulação de campo magnético, geralmente o fazem numa frequência de 5KHz ou 5,4KHz. A sequência de pulsos é breve e ocorre a cada batida do coração – para cada batida do coração, uma sequência de pulsos é enviada. A frequência da sequência de pulsos é de 5KHz e terá duração de 7ms a 13ms, mas isso poderá variar conforme o equipamento. Os equipamentos de ginástica de acadêmia que dão suporte ao uso da cinta de monitoramento cardíaco, geralmente exigem que a cinta utilize este esquema de transmissão, incluindo a frequência de 5KHz. Procurei saber se existe alguma norma ou especificação que padronize isto para os diferentes equipamentos de ginástica e cintas, mas não pesquisei o suficiente para concluir se há ou não normas sobre isto. o certo é que as cintas comerciais que utilizam este esquema de transmissão são compatíveis com esses equipamentos.

No caso das cintas da Polar com transmissores do tipo coded (codificado), estes, além de enviar um pulso a cada batida do coração, também, nos milissegundos posteriores, enviam duas outras sequências de pulsos. A ideia dos transmissores Polar coded é que a diferença de tempo entre a emissão das três sequências de pulsos para cada batida de coração deve ser variável em relação a diferentes transmissores, ou seja, cada transmissor coded enviará suas três sequências em tempos diferentes um do outro. Dessa forma, poderá um equipamento receptor de monitoramento poderá reconhecer que um dado conjunto de sinais é de uma determinada cinta e descartar os que não forem. Para isso, é necessário que inicialmente se utilize a cinta de monitoramento com o equipamento receptor de modo a estar longe de outras cintas transmissores, dessa forma o equipamento monitor poderá calcular a diferença de tempos entre as três sequências de pulsos do equipamento que se está utilizando e memorizar estes tempos para que posteriormente, quando se utilizar a cinta em ambiente onde haja outras cintas transmitindo, se possa identificar quais sequências de pulsos devem ser consideradas e quais não pelo equipamento receptor (relógio de monitoramento).

A vantagem deste sistema de campo magnético modulado, é que, na prática, ele não sofre interferência considerável dos sistemas de RF. Além disso, qualquer material não-metálico é praticamente transparente para o campo magnético e isto inclui o corpo humano e a água.

Eu fiquei de certa forma surpreso quando descobri que havia este sistema de transmissão. Note que se a transmissão fosse realizada via RF, o comprimento da onda de rádio seria de 60Km!!! Para quem conhece de eletrônica ou mesmo física básica, tem noção que isso é totalmente fora da faixa de rádio frequência comumente utilizado. Além disso, no caso de RF, quanto maior a onda, maior a energia necessária para criá-la. Também no caso de uma onda de comprimento de 60Km, a antena transmissora, por mais engenhosa que fosse, seria um tanto grande – eu creio. A bateria dessas cintas de monitoramento, duram entre meses a alguns anos, ou seja, essas cintas consomem pouca energia. Logo, quando se diz que a transmissão dessas cintas é realizada a 5KHz, se percebe que a transmissão não é via RF, mas nesse caso, via um simples campo magnético – no caso, que se faz oscilar a 5KHz, ou seja, se “liga” e “desliga” um campo magnético eletricamente formado.

Detectando o Campo Magnético Oscilante

É possível observar o sinal que a cinta de monitoramento emite para o aparelho receptor. Para tanto, será necessário o uso de uma cinta transmissora que realize a transmissão a 5KHz. Se for usar uma cinta Polar, opte por alguma que seja do tipo uncoded, ou seja, o tipo mais simples que tem. Caso utilize uma cinta Polar do tipo coded, deve-se levar em conta que esta transmite três sequências de pulsos ao invés de somente uma única sequência por batida do coração.

Para meus testes, inicialmente eu utilizei a cinta Polar coded que acompanha o RS300X e posteriormente, a vendemos e acabei conseguindo comprar uma Polar T31 uncoded usada, mas em bom estado e por um bom preço – custou R$ 78,00 no ML, já com o frete – e ainda veio com um conta giros do Polar S150 para a bike. Aqui em Brasília, numa feira que é conhecida como “Feira do Paraguay”, mas que rebatizada – hipocritamente – para “Feira dos Importados”, pode-se comprar a cinta transmissora Polar T31 non-coded por preços que variam entre R$ 85 a R$ 130. Mas como mencionei antes, qualquer cinta compatível de 5KHz, serve.

Para se perceber um campo magnético oscilante, pode-se utilizar uma simples bobina feita com fios. O campo magnético oscilante irá induzir uma pequena corrente elétrica na bobina. Para se ter ideia, dez voltas de fio comum encapado ou esmaltado enrolados numa caneta, já é suficiente para se detectar o campo magnético que é produzido pelas cintas de monitoramento cardíaco que utilizam este meio de transmissão. Faça o teste, enrole dez ou vinte voltas de fio encapado numa caneta ou lapis, ligue as duas extremidades do fio a um osciloscópio ou multímetro e aproxime ao máximo a bobina da cinta de monitoramento cardíaco. Lembre-se que a cinta só irá sinalizar o batimento do coração, ou seja, gerar o campo magnético oscilante a 5KHz durante um período de 5ms a 13ms, se você estiver usando essa cinta no peito, como normalmente faria. Na dúvida, utilize seu aparelho monitor (relógio usado com a cinta) para te indicar se a cinta está realmente transmitindo. Quando a cinta transmitir, você verá no osciloscópio um padrão senoidal de ondas com as características já mencionadas. No multímetro, verá uma pequena variação de tensão para cada batida do coração. Nota: se estiver usando uma cinta como as da Polar do tipo coded, poderá ver três sequências de pulsos no osciloscópio, ao invés de uma única por batida. As cintas codificadas da Polar irão atrapalhar a visualização do funcionamento do conceito de indução aqui descrito se usar um multímetro ao invés do osciloscópio – preferencialmente, use algum osciloscópio.

Se acaso não estiver detectando nada no osciloscópio, mesmo tendo certeza que a cinta está transmitindo, tente reposicionar a bobina ao longo do transmissor. Deixe a bobina o mais próximo possível do transmissor. Se mesmo assim não detectar nada, enrole o fio esmaltado ao redor do próprio transmissor da cinta e utilize uma fita adesiva para manter o fio devidamente enrolado – 20 a 30 espiras são suficientes. Comigo, de ambas as formas funcionaram, mas no fim, para não ter problema com a recepção quando estivesse usando a cinta e também para não aumentar o volume da cinta, preferi enrolar o fio diretamente em torno do transmissor da cinta.

Se for experiente com eletrônica analógica, amplificadores operacionais e filtros, com certeza ficará feliz em saber que poderá montar um receptor para sua cinta de monitoramento utilizando ampop ou preferencialmente, amplificadores de instrumentação ou mesmo tentando utilizar alguns JFET com amplificadores operacionais.

Também há vários projetos para recepção do sinal emitido pelas cinta de monitoramento que podem ser encontrados na web, veja alguns:

Polar Heart Rate Module – RMCM01

De todos os circuitos de recepção, com certeza, o mais fácil de se fazer, é utilizando o módulo RMCM01 da própria Polar. Este módulo receptor é um SOC – ele contém tudo o que é necessário para se captar o sinal transmitido por uma cinta de monitoramento cardíaco da Polar que transmita em 5KHz. O único componente externo que é requerido, é um cristal de 32KHz. Este módulo funciona tanto com as cintas coded como com as non-coded.

Quero muito testar este chip, mas não tive oportunidade (dinheiro) para fazê-lo. Este tipo de componente eletrônico não se encontra no Brasil e deve ser importado. Se acaso houver alguém que queira gentilmente ceder uma ou duas amostras deste chip, ficarei feliz de testá-lo e publicar o resultado dos testes aqui 🙂

Outras Formas de Detecção do Campo Magnético

Não cheguei a testar, mas creio ser possível utilizar um transistor de efeito hall com o objetivo de detectar o campo magnético.

A continuar…. (estou escrevendo)

Posted in development, electronic, gadgets, gym4us, it, projects, tips | Leave a comment

Meu Desejo: Monitoramento Cardíaco com o Nokia Sports Tracker

Introdução – O Início da Coisa (causa)

De tempos-em-tempos resolvo tomar atitude, ainda que temporariamente, e realizar exercícios físicos.

Não tenho problemas cardíacos e confirmei isso em recente bateria de exames. Já meu pai, teve muito problema com o coração, junto a muitas outras complicações de saúde. Como não sou tão jovem e sou sedentário, é prudente acompanhar meu desempenho cardíaco nos momentos de esforços físicos – coisa rara até meses atrás, momento em que percebi minha esposa participar de corridas de rua e concluí ser desejável acopanhá-la.

Há alguns anos atrás eu comprei um relógio com função de monitor cardíaco da Timex. Pouco usei e acabei por perder a cinta com o transmissor e isto ficou perdido até mês passado – de tão bem guardada, não me lembrava onde a havia deixado – por puro desuso. Já o relógio, esse ficava guardado na gaveta e todas as manhãs tocava o alarme às 07h00, mas não chegava a despertar ninguém, haja visto que ficava guardado no escritório. Quando a bateria acabava, eu a substituia, mesmo sem usá-lo, pois sempre me vinha a mente a cinta de monitoramento que havia perdido. E quando pensava em fazer exercício físico, novamente, me lembrava da cinta que devia estar guardada em algum lugar.

Recentemente, após recomeçar a realizar alguma atividade física com maior frequência, a necessidade de acompanhar meu desempenho, aumentou. Minha esposa que já praticava atividade física há mais tempo, acabou por decidir-se adquirir, para seu próprio uso, o relógio de monitoramento cardíaco da Polar – que pretenciosamente a Polar chama de &quot;computador de treino&quot; – de modelo RS300X G1. Fiquei intrigado com esse monitor da Polar, pois, apesar do preço salgado, não fazia muita coisa; o GPS que o acompanhava apenas servia para cálculo de velocidade e distância percorrida e nada mais fazia – se quer gravava o trajeto.

Desde o ano de 2008, quando ainda usava um Nokia N80, já conhecia o Nokia Sports Tracker e nos testes que havia realizado, o tinha considerado um brinquedo interessante e que se desenvolvido, poderia vir a ter utilidade. Três meses atrás, já retomado meu interesse em exercícios e acompanhar meu progresso, pesquisei e instalei a versão 1.83 do Nokia Sports Tracker para o Nokia 5800 XM. Fiquei gratamente surpreendido por ele estar com suporte a monitor cardíaco, ainda que proprietário. Ele requer um hardware que é fabricado pela Polar; no presente momento só é vendido conjuntamente ao smartphone Nokia N79 Active – e com mais nenhum outro produto e nem avulso como um acessório separado. Alguns dizem que esse hardware é na verdade o Polar Wearlink Bluetooth e que este é vendido unicamente em regime de OEM pela Polar; alguns outros chegaram a dizer que conseguiram comprar uma unidade de algum site que não vende mais; e o certo, é que isto ainda não é vendido pela Nokia e nem pela Polar para o cliente final. Se quiser ver a resposta ridícula que obtive da Nokia Brasil quando a questionei sobre se e quando o Nokia N79 Active seria vendido no Brasil ou se eles chegariam a comercializar a cinta de monitoramento compatível com o Nokia Sports Tracker (de agora em diante o chamarei de NST), leia o artigo Nokia N79 Active: Carta aberta para Nokia. Pesquisei soluções comerciais alternativas ao N79 e não encontrei nada satisfatório.

Cheguei a conclusão que seria muito legal ter um único eletrônico cumprindo a tarefa de vários outros, ou seja, falo de um smartphone que tivesse GPS, tocasse MP3, rodasse aplicações para acompanhamento de atividades fisícas, mostrasse mapinhas, armazenasse as informações para relatórios posteriores e extrações de dados, além de realizar o monitoramento cardíaco e permitisse o uso de serviços utilitários via web. Dito isto, o Nokia N79 Active ou qualquer solução semelhante, cai como uma luva. Consegui convencer minha esposa a vender o Polar RS300X G1 (e a $%$!#ria do Polar Flowlink) para comprar um N79 Active que encontrei a venda no ebay. História a parte, foi difícil encontrar um N79 Active para comprar. Só havia um único a venda em todo o ebay e até onde pesquisei na época, o N79 Active só era vendido em quatro países. Até hoje, passado mais de um mês, não recebemos o produto que compramos pelo ebay de um vendedor australiano que afirmou tê-lo ganhado num concurso por lá e, segundo ele, nunca chegou a ter usado o celular. Como disse, ainda estamos com esperanças desse equipamento chegar por aqui, apesar do vendedor não nos ter informado o código de postagem e ao contrário, ainda ter solicitado que nós enviássemos mais dinheiro, pois, segundo ele, o frete que o ebay calculou foi metade do valor que ele pagou para enviar o celular, lá da Austrália, para cá, Brasil. Ainda não consegui colocar minhas mãos nesse celular e o aguardo anciosamente para ver se foi bom negócio – e espero que seja, haja visto que minha esposa estava satisfeita com a solução da Polar, ainda que tivesse que carregar, ipod, celular, fones de ouvido com fio, G1 preso ao braço, relógio RS300X e cinta de monitoramento cardíaco.

Eu, sem perspectivas de encontrar minha cinta de monitoramento do meu Timex e ainda muito frustrado com a resposta da Nokia, sendo que queria mesmo era usar o Nokia Sports Tracker para o meu monitoramento cardíaco e evolução física, comecei a pesquisar soluções alternativas ao N79 Active. Isto começou após eu escrever o artigo Nokia N79 Active: Carta aberta para Nokia. Neste ponto, começa, o que acredito ser, uma série de novos artigos, onde descreverei minha busca e a solução que desenvolvi como resultado do meu desejo em monitorar minha atividade física com o auxílio de GPS (pode ser do próprio celular) e de um monitor cardíaco, preferencialmente, acoplados (entenda acoplado com algo que pode ser wireless…) a um smartphone.

Sobre o Projeto Gym4Us

http://picasaweb.google.com/s/c/bin/slideshow.swf

Tentarei postar – e se necessário for, atualizar este artigo – conforme for escrevendo. Sendo assim, peço um pouco de paciência daqueles que porventura estiverem interessados em minhas desaventuras e dou minha palavra que publicarei tudo até o fim com a solução final e funcionando no Nokia 5800 Xpress Music com o Nokia Sports Tracker v.2.06 e alguma cinta de monitoramento cardíaco, como a Polar T31 que é a que estou utilizando. Para quem não aguentar esperar, podem ver alguma coisa, em:

Adianto que diversas melhorias já estão programadas, incluindo modificação no tamanho do circuito, substituição do módulo de alimentação e do microcontrolador, além de um sistema que elimine a fiação externa. Também é possível integrar novos sensores e equipamentos, incluindo: sensores de temperatura, umidade, radiação solar, poluição atmosférica, giro de roda da bike, display para bike etc.

O uso do Gym4Us não está restrito ao Nokia Sports Tracker e facilmente pode-se usá-lo com outras soluções atuais e futuras. Desenvolvi aplicação J2ME que possibilita seu uso em qualquer celular com interpretador Java (J2ME) com suporte a API JSR-82 para uso do Bluetooth.

Detectando o Batimento do Coração

Na busca de uma solução, tive antes que entender o funcionamento de uma série de coisas, inclusive como funcionam as cintas de monitoramento cardíaco, seus sistemas de captação, amplificação, filtragem, detecção, codificação e transmissão. Além disso, também estudei um pouco sobre o ECG e as diferentes maneiras de se perceber o batimento do coração. Não sou médico, nem engenheiro, nem cientista; sou um mero programador que gosta de xeretar, portanto, de antemão, peço desculpas pelas tolices que falei e falarei até o fim deste artigo e de futuros artigos correlatos. A seguir, irei expor o que entendi e como isso me direcionou ao longo da pesquisa e desenvolvimento da solução final. Somente ao fim de todos os artigos irei compilar e acrescentar as informações necessárias para se compor o projeto em si, de forma simples, direta e técnica do ponto de vista do hardware e software; antes disso, haverá muito blah-blah-blahh de minha parte.

Nota: estou realizando novos posts em continuação a este artigo. Acompanhe no blog marciowb @ blog a tag Gym4Us.

Posted in development, electronic, gadgets, gym4us, it, projects | 4 Comments

Em Brasília, Bombeiros apagam fogo de carro em chamas

Hoje, ao sair do trabalho e já entrando no carro para ir para casa, me deparo com um carro em chamas bem em frente aonde estava. Curioso com o que ocorria, e vendo muitos carros pararem para tentar ajudar, resolvi filmar com meu smartphone Nokia 5800 XpressMusic e disponibilizar na web. As imagens são impressionantes.
Em Brasília, na 712 Sul, carro pega fogo

Comuniquei ao DFTV que tinha feito as imagens e eles as usaram no Radar DF de 02/02/2010. A reportagem pode ser vista nas páginas do website do Radar DF do DFTV.

O fato ocorreu na 712 Sul, aqui em Brasília, por volta das 18h43 do dia 01/02/2010, quando o carro pega fogo e bombeiros rapidamente chegam para apagar as chamas. Antes dos bombeiros chegarem, pode-se ver várias pessoas tentando ajudar a apaguer o incêndio com seus próprios extintores. Felizmente, o motorista do veículo escapou ileso, mas o carro foi completamente destruído em poucos minutos.

Esta é a segunda vez que vejo os bombeiros de Brasília em ação e como da outra vez, quando chegaram a fazer um resgate de helicoptero – pousando em plena L2 Norte em horário de rush – de forma a mostrarem destreza e preparo para esta gloriosa função, novamente, merecem meu mais profundo respeito e admiração. Parabéns a todos os bombeiros!!

Posted in Uncategorized | Leave a comment