Revision 1359
Added by Jing Tao almost 22 years ago
src/edu/ucsb/nceas/metacat/DBSAXHandler.java | ||
---|---|---|
43 | 43 |
import org.xml.sax.ext.LexicalHandler; |
44 | 44 |
import org.xml.sax.helpers.DefaultHandler; |
45 | 45 |
|
46 |
/**
|
|
46 |
/** |
|
47 | 47 |
* A database aware Class implementing callback bethods for the SAX parser to |
48 | 48 |
* call when processing the XML stream and generating events |
49 | 49 |
*/ |
50 |
public class DBSAXHandler extends DefaultHandler
|
|
50 |
public class DBSAXHandler extends DefaultHandler |
|
51 | 51 |
implements LexicalHandler, DeclHandler, Runnable { |
52 | 52 |
|
53 | 53 |
private boolean atFirstElement; |
... | ... | |
76 | 76 |
// DOCTITLE attr cleared from the db |
77 | 77 |
// private static final int MAXTITLELEN = 1000; |
78 | 78 |
|
79 |
/** Construct an instance of the handler class
|
|
79 |
/** Construct an instance of the handler class |
|
80 | 80 |
* |
81 | 81 |
* @param conn the JDBC connection to which information is written |
82 | 82 |
*/ |
... | ... | |
93 | 93 |
stackCreated = true; |
94 | 94 |
} |
95 | 95 |
} |
96 |
|
|
97 |
/** Construct an instance of the handler class
|
|
96 |
|
|
97 |
/** Construct an instance of the handler class |
|
98 | 98 |
* |
99 | 99 |
* @param conn the JDBC connection to which information is written |
100 | 100 |
* @param action - "INSERT" or "UPDATE" |
... | ... | |
106 | 106 |
* resides. |
107 | 107 |
* |
108 | 108 |
*/ |
109 |
public DBSAXHandler(DBConnection conn, String action, String docid,
|
|
109 |
public DBSAXHandler(DBConnection conn, String action, String docid, |
|
110 | 110 |
String user, String[] groups, String pub, int serverCode) |
111 | 111 |
{ |
112 | 112 |
this(conn); |
... | ... | |
117 | 117 |
this.pub = pub; |
118 | 118 |
this.serverCode = serverCode; |
119 | 119 |
this.xmlIndex = new Thread(this); |
120 |
}
|
|
121 |
|
|
120 |
} |
|
121 |
|
|
122 | 122 |
/** Construct an instance of the handler class |
123 | 123 |
* In this constructor, user can specify the version need to upadate |
124 | 124 |
* |
... | ... | |
133 | 133 |
* resides. |
134 | 134 |
* |
135 | 135 |
*/ |
136 |
public DBSAXHandler(DBConnection conn, String action, String docid,
|
|
136 |
public DBSAXHandler(DBConnection conn, String action, String docid, |
|
137 | 137 |
String revision, String user, String[] groups, String pub, int serverCode) |
138 | 138 |
{ |
139 | 139 |
this(conn); |
... | ... | |
146 | 146 |
this.serverCode = serverCode; |
147 | 147 |
this.xmlIndex = new Thread(this); |
148 | 148 |
} |
149 |
|
|
149 |
|
|
150 | 150 |
/** SAX Handler that receives notification of beginning of the document */ |
151 | 151 |
public void startDocument() throws SAXException { |
152 | 152 |
MetaCatUtil.debugMessage("start Document", 50); |
153 | 153 |
|
154 | 154 |
// Create the document node representation as root |
155 | 155 |
rootNode = new DBSAXNode(connection, this.docid); |
156 |
// Add the node to the stack, so that any text data can be
|
|
156 |
// Add the node to the stack, so that any text data can be |
|
157 | 157 |
// added as it is encountered |
158 | 158 |
nodeStack.push(rootNode); |
159 | 159 |
} |
... | ... | |
167 | 167 |
xmlIndex.start(); |
168 | 168 |
} catch (NullPointerException e) { |
169 | 169 |
xmlIndex = null; |
170 |
throw new
|
|
170 |
throw new |
|
171 | 171 |
SAXException("Problem with starting thread for writing XML Index. " + |
172 | 172 |
e.getMessage()); |
173 | 173 |
} |
174 | 174 |
} |
175 | 175 |
|
176 | 176 |
/** SAX Handler that is called at the start of Namespace */ |
177 |
public void startPrefixMapping(String prefix, String uri)
|
|
177 |
public void startPrefixMapping(String prefix, String uri) |
|
178 | 178 |
throws SAXException |
179 | 179 |
{ |
180 | 180 |
MetaCatUtil.debugMessage("NAMESPACE", 50); |
181 | 181 |
|
182 | 182 |
namespaces.put(prefix, uri); |
183 | 183 |
} |
184 |
|
|
184 |
|
|
185 | 185 |
/** SAX Handler that is called at the start of each XML element */ |
186 | 186 |
public void startElement(String uri, String localName, |
187 |
String qName, Attributes atts)
|
|
187 |
String qName, Attributes atts) |
|
188 | 188 |
throws SAXException { |
189 | 189 |
MetaCatUtil.debugMessage("Start ELEMENT " + qName, 50); |
190 |
|
|
190 |
|
|
191 | 191 |
DBSAXNode parentNode = null; |
192 | 192 |
DBSAXNode currentNode = null; |
193 | 193 |
|
... | ... | |
201 | 201 |
// Document representation that points to the root document node |
202 | 202 |
if (atFirstElement) { |
203 | 203 |
atFirstElement = false; |
204 |
// If no DOCTYPE declaration: docname = root element name
|
|
204 |
// If no DOCTYPE declaration: docname = root element name |
|
205 | 205 |
if (docname == null) { |
206 | 206 |
docname = localName; |
207 | 207 |
doctype = docname; |
... | ... | |
216 | 216 |
// for validated XML Documents store a reference to XML DB Catalog |
217 | 217 |
// Because this is select statement and it needn't to roll back if |
218 | 218 |
// insert document action fialed. |
219 |
// In order to decrease DBConnection usage count, we get a new
|
|
219 |
// In order to decrease DBConnection usage count, we get a new |
|
220 | 220 |
// dbconnection from pool |
221 | 221 |
String catalogid = null; |
222 | 222 |
DBConnection dbConn = null; |
223 | 223 |
int serialNumber = -1; |
224 |
|
|
224 |
|
|
225 | 225 |
if ( systemid != null ) { |
226 | 226 |
try |
227 | 227 |
{ |
... | ... | |
229 | 229 |
dbConn=DBConnectionPool.getDBConnection |
230 | 230 |
("DBSAXHandler.startElement"); |
231 | 231 |
serialNumber=dbConn.getCheckOutSerialNumber(); |
232 |
|
|
232 |
|
|
233 | 233 |
Statement stmt = dbConn.createStatement(); |
234 | 234 |
ResultSet rs = stmt.executeQuery( |
235 | 235 |
"SELECT catalog_id FROM xml_catalog " + |
236 |
"WHERE entry_type = 'DTD' " +
|
|
236 |
"WHERE entry_type = 'DTD' " + |
|
237 | 237 |
"AND public_id = '" + doctype + "'"); |
238 | 238 |
boolean hasRow = rs.next(); |
239 | 239 |
if ( hasRow ) { |
... | ... | |
247 | 247 |
DBConnectionPool.returnDBConnection(dbConn, serialNumber); |
248 | 248 |
}//finally |
249 | 249 |
} |
250 |
|
|
250 |
|
|
251 | 251 |
//create documentImpl object by the constructor which can specify |
252 | 252 |
//the revision |
253 |
currentDocument = new DocumentImpl(connection, rootNode.getNodeID(),
|
|
254 |
docname, doctype, docid, revision, action, user,
|
|
253 |
currentDocument = new DocumentImpl(connection, rootNode.getNodeID(), |
|
254 |
docname, doctype, docid, revision, action, user, |
|
255 | 255 |
this.pub, catalogid, this.serverCode); |
256 |
|
|
257 |
|
|
256 |
|
|
257 |
|
|
258 | 258 |
} catch (Exception ane) { |
259 |
throw (new SAXException("Error in DBSaxHandler.startElement " +
|
|
259 |
throw (new SAXException("Error in DBSaxHandler.startElement " + |
|
260 | 260 |
action, ane)); |
261 | 261 |
} |
262 |
}
|
|
262 |
} |
|
263 | 263 |
|
264 | 264 |
// Create the current node representation |
265 | 265 |
currentNode = new DBSAXNode(connection, qName, localName, parentNode, |
266 | 266 |
currentDocument.getRootNodeID(),docid, |
267 | 267 |
currentDocument.getDoctype()); |
268 |
|
|
268 |
|
|
269 | 269 |
// Add all of the namespaces |
270 | 270 |
String prefix; |
271 | 271 |
String nsuri; |
... | ... | |
281 | 281 |
// Add all of the attributes |
282 | 282 |
for (int i=0; i<atts.getLength(); i++) { |
283 | 283 |
currentNode.setAttribute(atts.getQName(i), atts.getValue(i), docid); |
284 |
}
|
|
284 |
} |
|
285 | 285 |
|
286 |
// Add the node to the stack, so that any text data can be
|
|
286 |
// Add the node to the stack, so that any text data can be |
|
287 | 287 |
// added as it is encountered |
288 | 288 |
nodeStack.push(currentNode); |
289 | 289 |
// Add the node to the vector used by thread for writing XML Index |
290 | 290 |
nodeIndex.addElement(currentNode); |
291 | 291 |
|
292 | 292 |
} |
293 |
|
|
293 |
|
|
294 | 294 |
/* The run method of xmlIndex thread. It writes XML Index for the document. */ |
295 | 295 |
public void run () { |
296 | 296 |
DBSAXNode currNode = null; |
... | ... | |
302 | 302 |
int counter = 0; |
303 | 303 |
|
304 | 304 |
try { |
305 |
|
|
305 |
|
|
306 | 306 |
// Opening separate db connection for writing XML Index |
307 | 307 |
dbConn=DBConnectionPool.getDBConnection("DBSAXHandler.run"); |
308 | 308 |
serialNumber=dbConn.getCheckOutSerialNumber(); |
309 | 309 |
dbConn.setAutoCommit(false); |
310 |
|
|
310 |
|
|
311 | 311 |
//the following while loop construct checks to make sure that the docid |
312 | 312 |
//of the document that we are trying to index is already |
313 | 313 |
//in the xml_documents table. if this is not the case, the foreign |
... | ... | |
317 | 317 |
while(!inxmldoc) |
318 | 318 |
{ |
319 | 319 |
String xmlDocumentsCheck = "select distinct docid from xml_documents"; |
320 |
PreparedStatement xmlDocCheck =
|
|
320 |
PreparedStatement xmlDocCheck = |
|
321 | 321 |
dbConn.prepareStatement(xmlDocumentsCheck); |
322 | 322 |
// Increase usage count |
323 | 323 |
dbConn.increaseUsageCount(1); |
... | ... | |
325 | 325 |
ResultSet doccheckRS = xmlDocCheck.getResultSet(); |
326 | 326 |
boolean tableHasRows = doccheckRS.next(); |
327 | 327 |
Vector docids = new Vector(); |
328 |
while(tableHasRows)
|
|
328 |
while(tableHasRows) |
|
329 | 329 |
{ |
330 | 330 |
docids.add(doccheckRS.getString(1).trim()); |
331 | 331 |
tableHasRows = doccheckRS.next(); |
332 | 332 |
} |
333 |
|
|
333 |
|
|
334 | 334 |
for(int i=0; i<docids.size(); i++) |
335 | 335 |
{ |
336 | 336 |
String d = ((String)docids.elementAt(i)).trim(); |
... | ... | |
341 | 341 |
} |
342 | 342 |
xmlDocCheck.close(); |
343 | 343 |
} |
344 |
|
|
344 |
|
|
345 | 345 |
// Going through the elements of the document and writing its Index |
346 | 346 |
Enumeration nodes = nodeIndex.elements(); |
347 | 347 |
while ( nodes.hasMoreElements() ) { |
348 | 348 |
currNode = (DBSAXNode)nodes.nextElement(); |
349 | 349 |
currNode.updateNodeIndex(dbConn, docid, doctype); |
350 | 350 |
} |
351 |
|
|
352 |
|
|
351 |
|
|
352 |
|
|
353 | 353 |
dbConn.commit(); |
354 |
|
|
354 |
|
|
355 | 355 |
//if this is a package file |
356 | 356 |
String packagedoctype = MetaCatUtil.getOption("packagedoctype"); |
357 | 357 |
Vector packagedoctypes = new Vector(); |
358 |
|
|
358 |
|
|
359 | 359 |
packagedoctypes = MetaCatUtil.getOptionList(packagedoctype); |
360 |
|
|
360 |
|
|
361 | 361 |
if ( packagedoctypes.contains(doctype) ) |
362 | 362 |
{ |
363 | 363 |
// write the package info to xml_relation table |
... | ... | |
368 | 368 |
if ( aclid != null ) { |
369 | 369 |
runAccessControlList(dbConn, aclid); |
370 | 370 |
} |
371 |
|
|
371 |
|
|
372 | 372 |
} |
373 |
|
|
373 |
|
|
374 | 374 |
// if it is an access file |
375 | 375 |
else if ( MetaCatUtil.getOptionList( |
376 | 376 |
MetaCatUtil.getOption("accessdoctype")).contains(doctype) ) |
377 | 377 |
{ |
378 | 378 |
// write ACL for the package |
379 | 379 |
//runAccessControlList(dbConn, docid); |
380 |
|
|
380 |
|
|
381 | 381 |
} |
382 |
|
|
383 |
|
|
382 |
|
|
383 |
|
|
384 | 384 |
//dbconn.close(); |
385 | 385 |
|
386 | 386 |
} catch (Exception e) { |
... | ... | |
394 | 394 |
finally |
395 | 395 |
{ |
396 | 396 |
DBConnectionPool.returnDBConnection(dbConn, serialNumber); |
397 |
}//finally
|
|
397 |
}//finally |
|
398 | 398 |
} |
399 |
|
|
399 |
|
|
400 | 400 |
// It runs in xmlIndex thread. It writes ACL for a package. |
401 | 401 |
private void runAccessControlList (DBConnection conn, String aclid) |
402 | 402 |
throws Exception |
403 | 403 |
{ |
404 | 404 |
// read the access file from xml_nodes |
405 | 405 |
// parse the access file and store the access info into xml_access |
406 |
AccessControlList aclobj =
|
|
406 |
AccessControlList aclobj = |
|
407 | 407 |
new AccessControlList(conn, aclid, //new StringReader(xml), |
408 | 408 |
user, groups, serverCode); |
409 | 409 |
conn.commit(); |
... | ... | |
418 | 418 |
int leftover = len; |
419 | 419 |
int offset = start; |
420 | 420 |
boolean moredata = true; |
421 |
|
|
422 |
// This loop deals with the case where there are more characters
|
|
423 |
// than can fit in a single database text field (limit is
|
|
421 |
|
|
422 |
// This loop deals with the case where there are more characters |
|
423 |
// than can fit in a single database text field (limit is |
|
424 | 424 |
// MAXDATACHARS). If the text to be inserted exceeds MAXDATACHARS, |
425 | 425 |
// write a series of nodes that are MAXDATACHARS long, and then the |
426 | 426 |
// final node contains the remainder |
... | ... | |
439 | 439 |
} |
440 | 440 |
} |
441 | 441 |
|
442 |
/**
|
|
442 |
/** |
|
443 | 443 |
* SAX Handler that is called for each XML text node that is |
444 | 444 |
* Ignorable white space |
445 | 445 |
*/ |
... | ... | |
449 | 449 |
// When validation is turned "off" white spaces are not reported here, |
450 | 450 |
// but through characters() callback |
451 | 451 |
MetaCatUtil.debugMessage("IGNORABLEWHITESPACE", 50); |
452 |
|
|
453 | 452 |
|
453 |
|
|
454 | 454 |
DBSAXNode currentNode = (DBSAXNode)nodeStack.peek(); |
455 | 455 |
String data = null; |
456 | 456 |
int leftover = len; |
457 | 457 |
int offset = start; |
458 | 458 |
boolean moredata = true; |
459 |
|
|
460 |
// This loop deals with the case where there are more characters
|
|
461 |
// than can fit in a single database text field (limit is
|
|
459 |
|
|
460 |
// This loop deals with the case where there are more characters |
|
461 |
// than can fit in a single database text field (limit is |
|
462 | 462 |
// MAXDATACHARS). If the text to be inserted exceeds MAXDATACHARS, |
463 | 463 |
// write a series of nodes that are MAXDATACHARS long, and then the |
464 | 464 |
// final node contains the remainder |
... | ... | |
477 | 477 |
} |
478 | 478 |
} |
479 | 479 |
|
480 |
/**
|
|
481 |
* SAX Handler called once for each processing instruction found:
|
|
480 |
/** |
|
481 |
* SAX Handler called once for each processing instruction found: |
|
482 | 482 |
* node that PI may occur before or after the root element. |
483 | 483 |
*/ |
484 |
public void processingInstruction(String target, String data)
|
|
484 |
public void processingInstruction(String target, String data) |
|
485 | 485 |
throws SAXException { |
486 | 486 |
MetaCatUtil.debugMessage("PI", 50); |
487 | 487 |
DBSAXNode currentNode = (DBSAXNode)nodeStack.peek(); |
... | ... | |
502 | 502 |
// |
503 | 503 |
|
504 | 504 |
/** SAX Handler that receives notification of DOCTYPE. Sets the DTD */ |
505 |
public void startDTD(String name, String publicId, String systemId)
|
|
505 |
public void startDTD(String name, String publicId, String systemId) |
|
506 | 506 |
throws SAXException { |
507 | 507 |
docname = name; |
508 | 508 |
doctype = publicId; |
509 | 509 |
systemid = systemId; |
510 | 510 |
|
511 |
processingDTD = true; |
|
512 |
|
|
511 | 513 |
MetaCatUtil.debugMessage("Start DTD", 50); |
514 |
MetaCatUtil.debugMessage("Setting processingDTD to true", 50); |
|
512 | 515 |
MetaCatUtil.debugMessage("DOCNAME: " + docname, 50); |
513 | 516 |
MetaCatUtil.debugMessage("DOCTYPE: " + doctype, 50); |
514 | 517 |
MetaCatUtil.debugMessage(" SYSID: " + systemid, 50); |
515 | 518 |
} |
516 | 519 |
|
517 |
/**
|
|
518 |
* SAX Handler that receives notification of end of DTD
|
|
520 |
/** |
|
521 |
* SAX Handler that receives notification of end of DTD |
|
519 | 522 |
*/ |
520 | 523 |
public void endDTD() throws SAXException { |
521 |
|
|
524 |
|
|
525 |
processingDTD = true; |
|
526 |
MetaCatUtil.debugMessage("Setting processingDTD to false", 50); |
|
522 | 527 |
MetaCatUtil.debugMessage("end DTD", 50); |
523 | 528 |
} |
524 | 529 |
|
525 |
/**
|
|
530 |
/** |
|
526 | 531 |
* SAX Handler that receives notification of comments in the DTD |
527 | 532 |
*/ |
528 | 533 |
public void comment(char[] ch, int start, int length) throws SAXException { |
... | ... | |
533 | 538 |
} |
534 | 539 |
} |
535 | 540 |
|
536 |
/**
|
|
541 |
/** |
|
537 | 542 |
* SAX Handler that receives notification of the start of CDATA sections |
538 | 543 |
*/ |
539 | 544 |
public void startCDATA() throws SAXException { |
540 | 545 |
MetaCatUtil.debugMessage("start CDATA", 50); |
541 | 546 |
} |
542 | 547 |
|
543 |
/**
|
|
548 |
/** |
|
544 | 549 |
* SAX Handler that receives notification of the end of CDATA sections |
545 | 550 |
*/ |
546 | 551 |
public void endCDATA() throws SAXException { |
547 | 552 |
MetaCatUtil.debugMessage("end CDATA", 50); |
548 | 553 |
} |
549 | 554 |
|
550 |
/**
|
|
555 |
/** |
|
551 | 556 |
* SAX Handler that receives notification of the start of entities |
552 | 557 |
*/ |
553 | 558 |
public void startEntity(String name) throws SAXException { |
... | ... | |
558 | 563 |
} |
559 | 564 |
} |
560 | 565 |
|
561 |
/**
|
|
566 |
/** |
|
562 | 567 |
* SAX Handler that receives notification of the end of entities |
563 | 568 |
*/ |
564 | 569 |
public void endEntity(String name) throws SAXException { |
... | ... | |
569 | 574 |
} |
570 | 575 |
} |
571 | 576 |
|
572 |
/**
|
|
577 |
/** |
|
573 | 578 |
* SAX Handler that receives notification of element declarations |
574 | 579 |
*/ |
575 | 580 |
public void elementDecl(String name, String model) |
... | ... | |
578 | 583 |
MetaCatUtil.debugMessage("ELEMENTDECL: " + name + " " + model, 50); |
579 | 584 |
} |
580 | 585 |
|
581 |
/**
|
|
586 |
/** |
|
582 | 587 |
* SAX Handler that receives notification of attribute declarations |
583 | 588 |
*/ |
584 | 589 |
public void attributeDecl(String eName, String aName, |
585 | 590 |
String type, String valueDefault, String value) |
586 | 591 |
throws org.xml.sax.SAXException { |
587 | 592 |
|
588 |
//System.out.println("ATTRIBUTEDECL: " + eName + " "
|
|
593 |
//System.out.println("ATTRIBUTEDECL: " + eName + " " |
|
589 | 594 |
// + aName + " " + type + " " + valueDefault + " " |
590 | 595 |
// + value); |
591 |
MetaCatUtil.debugMessage("ATTRIBUTEDECL: " + eName + " "
|
|
596 |
MetaCatUtil.debugMessage("ATTRIBUTEDECL: " + eName + " " |
|
592 | 597 |
+ aName + " " + type + " " + valueDefault + " " |
593 | 598 |
+ value, 50); |
594 | 599 |
} |
595 | 600 |
|
596 |
/**
|
|
601 |
/** |
|
597 | 602 |
* SAX Handler that receives notification of internal entity declarations |
598 | 603 |
*/ |
599 | 604 |
public void internalEntityDecl(String name, String value) |
... | ... | |
602 | 607 |
MetaCatUtil.debugMessage("INTERNENTITYDECL: " + name + " " + value, 50); |
603 | 608 |
} |
604 | 609 |
|
605 |
/**
|
|
610 |
/** |
|
606 | 611 |
* SAX Handler that receives notification of external entity declarations |
607 | 612 |
*/ |
608 | 613 |
public void externalEntityDecl(String name, String publicId, |
609 | 614 |
String systemId) |
610 | 615 |
throws org.xml.sax.SAXException { |
611 |
//System.out.println("EXTERNENTITYDECL: " + name + " " + publicId
|
|
616 |
//System.out.println("EXTERNENTITYDECL: " + name + " " + publicId |
|
612 | 617 |
// + " " + systemId); |
613 |
MetaCatUtil.debugMessage("EXTERNENTITYDECL: " + name + " " + publicId
|
|
618 |
MetaCatUtil.debugMessage("EXTERNENTITYDECL: " + name + " " + publicId |
|
614 | 619 |
+ " " + systemId, 50); |
615 | 620 |
// it processes other external entity, not the DTD; |
616 | 621 |
// it doesn't signal for the DTD here |
... | ... | |
621 | 626 |
// the next section implements the ErrorHandler interface |
622 | 627 |
// |
623 | 628 |
|
624 |
/**
|
|
629 |
/** |
|
625 | 630 |
* SAX Handler that receives notification of fatal parsing errors |
626 | 631 |
*/ |
627 | 632 |
public void fatalError(SAXParseException exception) throws SAXException { |
... | ... | |
629 | 634 |
throw (new SAXException("Fatal processing error.", exception)); |
630 | 635 |
} |
631 | 636 |
|
632 |
/**
|
|
637 |
/** |
|
633 | 638 |
* SAX Handler that receives notification of recoverable parsing errors |
634 | 639 |
*/ |
635 | 640 |
public void error(SAXParseException exception) throws SAXException { |
... | ... | |
637 | 642 |
throw (new SAXException("Processing error.", exception)); |
638 | 643 |
} |
639 | 644 |
|
640 |
/**
|
|
645 |
/** |
|
641 | 646 |
* SAX Handler that receives notification of warnings |
642 | 647 |
*/ |
643 | 648 |
public void warning(SAXParseException exception) throws SAXException { |
... | ... | |
645 | 650 |
throw (new SAXException("Warning.", exception)); |
646 | 651 |
} |
647 | 652 |
|
648 |
//
|
|
653 |
// |
|
649 | 654 |
// Helper, getter and setter methods |
650 | 655 |
// |
651 |
|
|
656 |
|
|
652 | 657 |
/** |
653 | 658 |
* get the document name |
654 | 659 |
*/ |
Also available in: Unified diff
Merge the code for monarch.