Showing posts with label Oracle. Show all posts
Showing posts with label Oracle. Show all posts

Tuesday, December 01, 2009

Database Partitioning

Partitioning

What is it?
Splitting storage of data based on a key into different tables. End users of data don't generally "see" the partitioning, but do receive performance benefits. The key should be a value that is always available, such as user id.

Oracle Partitioning
Oracle partitioning creates sub tables for a given table and is defined when the table is created. Records are put into sub tables based on a key defined when the table is created.

CREATE TABLE mytable(
my_id NUMBER( 20 ) NOT NULL,
...
)
PARTITION BY HASH (MY_ID) -- Hash on MY_ID
PARTITIONS ???
STORE IN (tablespace_name)
/


Oracle partitioning limits:
- cannot span across hosts
- can't do an update to a row that would change which sub table the row is stored in-- if you want to move the record to another subtable, you must deleted the row and insert it again

Partitioning schemes
hash(column_name) (must understand Oracle hash function well to debug)
integer_column mod 10 (easy to debug, but can only span 2 or 5 sub tables)
integer_column mod 12 (harder to debug, but can be spanned across 6,4,3 or 2 sub tables)

Logical Partitioning/Sharding
It is possible to implement partitioning at the data access layer. Hibernate calls this sharding.

Mixing of sharding and partitioning
Temporal data

Uses of partitioning
Partitioning by geographic location (so user data is stored near their physical location)
partition by date

Friday, October 16, 2009

DB Usage Tips from eBay

Tip #1: Don't use transactions
instead, each record has a status and a version #
For inserts:
  1. add parent
  2. add children
  3. set children status to active
  4. set parent status to active
For updates:
  1. insert parent w/ higher version #
  2. insert children w/ higher version #
  3. set children status to active
  4. set parent to active
If a failure occurs, clean up orphans.

Tip #2: no joins allowed
Use lots of tables instead (easier to partition)
Joins can easily overload a database.
Offload logic to easily load balanced app servers
db is just data in and out

Tip #3: Limit # of returned rows using where clause
>20,000 rows returned is threshold
100k allowed in some cases
Even if you could get 100k results, probably can't process them.

Wednesday, September 16, 2009

Using Oracle Sequences with JPA

Oracle doesn't allow auto increment like MySQL. Auto increment, on big applications can cause all sorts of problems. Most JPA examples are targeted for MySQL.

First, I use Number(19) for id columns.

Second, the generator name is not important so long as it is unique and generator in GeneratedValue annotation matches name in SequenceGenerator annotation.

Sequence Name in SequenceGenerator is the name of your Oracle sequence.

Example entity code:

import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.SequenceGenerator;
...
@Id
@Basic(optional = false)
@Column(name = "MY_ID")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "MYNOTIFSEQ")
@SequenceGenerator(name="MYNOTIFSEQ", sequenceName = "MY_SEQ")
private Long userNotificationId;
...


Here's a sample sequence:
------------------------------------
--
-- Sequence MY_SEQ
--
------------------------------------
CREATE SEQUENCE MY_SEQ
INCREMENT BY 1
START WITH 1
MINVALUE 1
MAXVALUE 999999999999999999
NOORDER
CACHE 20
CYCLE
/

Tuesday, January 06, 2009

Accessing HTTPS pages using Oracle's UTL_HTTP package

The change needed to your PL/SQL is straightforward. Simply configure UTL_HTTP to use the "Oracle wallet":

UTL_HTTP.SET_WALLET(v_wallet_path,v_wallet_password);


The hard bit is importing certificates into a wallet.

  1. Start Oracle Wallet Manager
    # xhost +
    access control disabled, clients can connect from any host
    # su - oracle
    Sun Microsystems Inc. SunOS 5.10 Generic January 2005
    -bash-3.00$ export DISPLAY=:1 (if you are using VNC)
    -bash-3.00$ owm

    If the DB is on another machine, do the following:
    -bash-3.00$ export DISPLAY=192.168.1.58:1
    -bash-3.00$ owm
  2. Hit the new wallet button
  3. "Your default wallet does not exist, do you want to create it?"
    No
  4. Prompt for wallet password. Enter whatever you prefer.
  5. "A new empty wallet has been created. Do you want to create a new certificate request at this time?"
    No
  6. Right click on trusted certificates in the hierarchy on the left
  7. Choose Import Certificate
  8. "Choose a method to select the certificate"
    ( ) Paste the certificate
    (x) Load the certificate from a file
  9. Go to another terminal

    Here are the instructions for Sun Appserver
  10. # cd /var/opt/SUNWappserver/domains/domain1/config
  11. List certificates:
    /opt/SUNWappserver/appserver/lib/certutil -L -d .
  12. You should see s1as in the list. Then type:
    /opt/SUNWappserver/appserver/lib/pk12util -o ascerts.p12 -n s1as -d .
  13. This spits out a pkcs12 format certificate.

    Now onto OpenSSL:
  14. Oracle wallet requires x509, you need to use openssl to convert to pem and then to x509.
    # ./openssl pkcs12 -in /export/home/admin/ascerts.p12 -clcerts -nokeys -out satin.acceleresystems.com.cert.pem
    Enter Import Password:
    MAC verified OK
    # ./openssl pkcs12 -in /export/home/admin/ascerts.p12 -nocerts -nodes -out satin.acceleresystems.com.key.pem

    Enter Import Password:
    MAC verified OK
  15. Now convert to X509
    # ./openssl x509 -in satin.acceleresystems.com.cert.pem -out satin.acceleresystems.com.cert.x509
  16. Copy file to the machine with Oracle, and finish the import using OWM
  17. Save the wallet to /export/home/oracle/wallets. You can't set the name, but it will be ewallet.p12. To use the wallet you only need the directory name (see next step).
  18. You can now access the wallet from Oracle using UTL_HTTP.set_wallet('file:/export/home/oracle/wallets','password');
You MUST use the absolute path to the cert when using openssl, or you will get an error about TRUSTED certificate on the last step of openssl x509.

If editing an existing wallet, you need to hit open and navigate to the wallets directory. Continue even though you have no default set.

"Printf" Debugging XML in Oracle

Oracle has a package called "DBMS_OUTPUT" which has a function called put_line that is handy for doing "printf" debugging. Unfortunately it doesn't work with XML. Here's a procedure that does it for you.

create or replace procedure dbms_output_xml (p_xml in xmltype)
is
l_str long;
begin
l_str := p_xml.extract('/*').getstringval();
loop
exit when l_str is null;
dbms_output.put_line (substr (l_str, 1, instr (l_str, chr(10)) - 1));
l_str := substr (l_str, instr (l_str, chr(10)) + 1);
end loop;
end dbms_output_xml;

Grabbing a Webpage Greater than 32k using UTL_HTTP

The UTL_HTTP package in Oracle allows PL/SQL to grab a webpage and save it into an Oracle variable. The trouble is, it returns a Varchar2 and this can be up to 32k. The solution is to use the DBMS_LOB package to write to a CLOB. A loop gets the 32k chunks and appends them to the CLOB. Straightforward enough but it takes some time to get it all working. Here's the code:

CREATE OR REPLACE PROCEDURE load_html_from_url (p_url  IN  VARCHAR2, p_html out clob) AS
l_http_request UTL_HTTP.req;
l_http_response UTL_HTTP.resp;
l_clob CLOB;
l_text VARCHAR2(32767);
BEGIN
-- Initialize the CLOB.
DBMS_LOB.createtemporary(l_clob, FALSE);

-- Make a HTTP request and get the response.
l_http_request := UTL_HTTP.begin_request(p_url);
l_http_response := UTL_HTTP.get_response(l_http_request);

-- Copy the response into the CLOB.
BEGIN
LOOP
UTL_HTTP.read_text(l_http_response, l_text, 32767);
DBMS_LOB.writeappend (l_clob, LENGTH(l_text), l_text);
END LOOP;
EXCEPTION
WHEN UTL_HTTP.end_of_body THEN
UTL_HTTP.end_response(l_http_response);
END;

p_html:= l_clob;
EXCEPTION
WHEN OTHERS THEN
UTL_HTTP.end_response(l_http_response);
-- Relase the resources associated with the temporary LOB.
DBMS_LOB.freetemporary(l_clob);
RAISE;
END load_html_from_url;


Note: if you want to use HTTP POST instead of GET, you will need to create your request differently. Use
l_http_request := UTL_HTTP.begin_request(v_url, 'POST', 'HTTP/1.1');
utl_http.write_text(l_http_request, v_message);


It may also be necessary to specify the content length like so:
utl_http.set_header(l_http_request, 'Content-Length', length(v_message));

Oracle XML Queries and Namespaces

Oracle XML functions have trouble dealing with namespaces. It seems the best way it to simply strip them out but if you can't, you might try and use the optional namespace argument in many of the functions. Trouble is that Oracle's documentation doesn't actually explain what the syntax is.

The answer comes from Flavio Casetta at his oraclequirks blog:

select 
extractValue(content,'/path/here','xmlns="http://www.w3.org/2005/Atom"') contentname
from
table

Monday, October 13, 2008

Exporting Table Data as Oracle XML

If you are using dbms xmlsave, you might want to capture data from a table into Oracle's format as well. This is actually very straightforward with dbms_xmlgen.

SELECT dbms_xmlgen.getXml(
'SELECT *
FROM your_table_name_goes_here'
, 0
) FROM dual;

Oracle SQL predefined exceptions

If you've written PL/SQL stored procedures and functions, you may have use BEGIN/END blocks with EXCEPTION. Basically, this is kind of like try/catch in Java, and you can catch different exceptions and handle them. The syntax is like this:

BEGIN
-- your code goes here. If there isn't any, put NULL;
NULL;
EXCEPTION
WHEN NO_DATA_FOUND OR BLAHBLAH THEN
...
WHEN OTHERS THEN
END;


To get a list of the constants ("predefined exceptions") that you can catch, look here.

Tuesday, October 07, 2008

Looping over a Set of Nodes with PL/SQL

Oracle's XMLType has the handy member function 'extract' which evaluates an XPath. Except it won't work if you XPath is a function call. So, if you want to count how many children there are of a node so you can iterate over them,
extract('count(/xpath/here/*)')
won't work. Here's the solution:
select count(*) into v_maxi from TABLE(XMLSequence(extract(p_xml,'/xpath/here')));               
FOR v_i IN 0..v_maxi-1 LOOP
v_xmlfrag := p_xml.extract('/xpath/here[count(preceding-sibling::*)='||v_i||']');
v_text := v_xmlfrag.extract('/record/stuff/text()').getStringVal();
END LOOP;

Insert, Append, or Update an XML element to an XML document using Oracle PL/SQL

Oracle allows you to quickly insert or append an XML element to a path inside an XML document using insertchildxml.

select
insertchildxml(v_xmltype,
'/my/xpath',
lower('elementname'),
XMLType('stuff'))
from dual;


Gotchas: You must use a select statement. Argument 3 must match the element inside argument 4.

If you are modifying attributes, you can use updatexml-- but it is obnoxious because if the attribute doesn't exist, updatexml fails silently. So, if unsure, you need to user insertxml to add the attribute and if it throws an exception, then attempt an update. Like so:

   BEGIN
select insertchildxml(v_xml, '//myxpath', '@myattribute',
v_newvalue) into v_xml from dual;
EXCEPTION -- if @myattribute attribute exists, insert will fail, so update instead
WHEN OTHERS THEN
select updatexml(v_xml, '//myxpath/@myattribute', v_newvalue)
into v_xml from dual;
END;

Extending DBMS.XMLSave to Insert into Multiple Tables

Oracle, through the DBMS.XMLSave package, is able to take XML in a specific format and insert it into normal relational tables. This package has two major limitations in my opinion: 1) The values of rows are NOT evaluated, so functions do not work and sequences don't work. 2) Only one target table can be specified.

I've written a procedure which wraps Oracle's format inside some higher level XML elements to allow insertion into multiple tables. My solution for sequences and functions is not very flexible, but it will work for specific applications, and I can make some recommendations on how to improve the flexibility.

Let's start with inserting into multiple tables. If you look at my previous post, you will see Oracle's format for the DBMS.XMLSave function. I wrap it in two higher level elements, records (the new root) and the record element which contains a rowset.

<records>
<record table="tablenamegoeshere">
<rowset>
<row>
<columnname>value</columnname>
</row>
</rowset>
</record>
<records>


A stored procedure to handle these XML files must loop through each record and call Oracle's XMLSave on the rowset contained within. Each record element has an attribute, table, which specifies which table to insert into.

The next step is to implement sequences. We could take a cue from Hibernate and add an attribute to the column that uses a sequence. I didn't have time to implement this, so what I've done is update one column in each row with the results of an execute immediate. This assumes that there is only one sequence that needs updating and that it's name & and the containing column's name is based on the table attribute in the record element.

I use a similar procedure for function calls-- use XPath to update columns. The structure of the data is very rigid unfortunately because of this.

One gotcha -- make sure rowset and row elements are LOWER CASE. Oracle's procedures are case sensitive.

create or replace procedure PARSE_RECORD_XML(p_xml in xmltype, p_xmlid in integer) is
v_i INTEGER;
v_j INTEGER;
v_func_arg INTEGER;
v_seqnum INTEGER;
v_func_result VARCHAR2(50);
v_xmlfrag XMLTYPE;
v_tablename VARCHAR2(50);
v_seqname VARCHAR2(100);
v_maxi INTEGER;
v_maxj INTEGER;

begin
select count(*) into v_maxi from TABLE(XMLSequence(extract(p_xml,'/records/record')));
FOR v_i IN 0..v_maxi-1 LOOP
v_xmlfrag := p_xml.extract('/records/record[count(preceding-sibling::*)='||v_i||']');
v_tablename := v_xmlfrag.extract('/record/@table').getStringVal();
v_seqname := v_tablename || 'ID'; -- sequence name is a concatenation of table attribute and ID

select count(*) into v_maxj from TABLE(XMLSequence(extract(v_xmlfrag,'/record/rowset/row')));

FOR v_j in 0..v_maxj-1 LOOP
-- Get sequence number
execute immediate 'SELECT ' || v_seqname || '.nextval from dual' into v_seqnum;

-- Execute a function on some data
v_func_result := v_xmlfrag.extract('/record/rowset/row[count(preceding-sibling::*)='||v_j||']/func_arg/text()').getStringVal();

-- Insert the parent id
select insertchildxml(v_xmlfrag, '/record/rowset/row[count(preceding-sibling::*)='||v_j||']', 'parentid',
XMLType('' || p_xmlid || '')) into v_xmlfrag from dual;

-- Insert function result
select insertchildxml(v_xmlfrag, '/record/rowset/row[count(preceding-sibling::*)='||v_j||']', 'func_result',
XMLType('' || getMRN(v_patientid) || '')) into v_xmlfrag from dual;

-- Insert sequence id
select insertchildxml(v_xmlfrag, '/record/rowset/row[count(preceding-sibling::*)='||v_j||']', lower(v_seqname),
XMLType('<' || lower(v_seqname) || '>' || v_seqnum || '')) into v_xmlfrag from dual;


END LOOP;
InsertXML(v_xmlfrag.extract('/record/rowset'), v_tablename);
END LOOP;
end PARSE_RECORD_XML;

Inserting XML data into an Oracle Table

Oracle has a package DBMS_XMLsave that can take an XML document and insert it into a normal relational table. The format for the XML document is

<ROWSET>
<ROW>
<COLUMNNAME>
value
</COLUMNNAME>
</ROW>
</ROWSET>


The other argument to the save function is the table name. Two gotchas: 1) by default, the element names are case sensitive. I think this is a nightmare with table column names, so luckily there is a flag to disable case sensitivity (why do developer's inflict case sensitivity on each other?) 2) The format is very barebones. Attributes are ignored and you can't use functions within the values for columns. You can only insert into one table as well. I've created some code to get around this, which is the topic of another post.

At least, though, you CAN use XSLT to transform your document to something that Oracle can handle natively.

Here is a stored procedure that inserts such an XML document:

CREATE OR REPLACE PROCEDURE InsertXML (p_xml in xmltype, p_tablename in varchar2) is

queryCtx DBMS_XMLsave.ctxType;

rowsInserted INT;

BEGIN

--Defines the query

queryCtx := SYS.DBMS_XMLSave.newContext(p_tablename);

DBMS_XMLSave.setIgnoreCase(queryCtx, 1);

rowsInserted := DBMS_XMLSave.insertXML(queryCtx,p_xml.getCLOBval());

DBMS_XMLSave.closeContext(queryCtx);

commit;

END;

Wednesday, August 20, 2008

Creating User Defined Types in Oracle

You can roll your own types in Oracle (and Postgres and mysql also) that include member variables and procedures though the procedures won't be portable. Here is how to create and use a type that has no procedures:

create or replace type MYUDT as object
(
-- Author : HELMUT
-- Created : 8/20/2008 10:36:10 AM
-- Purpose : example user defined type

-- Attributes
SILLY NUMBER,
FOO VARCHAR2(200)

-- Member functions and procedures

)


Create a table like normal:

-- Create table
create table DUMMYTABLE
(
USERTYPECOLUMN MYUDT
)
;


You can then insert into the table like so:

INSERT INTO DUMMYTABLE (USERTYPECOLUMN) VALUES ( MYUDT(10,'Obamarama') );


and read from the table like so:

SELECT USERTYPECOLUMN.FOO FROM DUMMYTABLE WHERE USERTYPECOLUMN.SILLY > 5;

Thursday, July 17, 2008

Passing XML stored in a String into Oracle Stored Procs

For stored procedures, you can pass XML data stored in a java.lang.String into a procedure via a CLOB type. To cofigure you Oracle callablestatement, use setObject on the java.lang.String. It's harder to pass in XMLType because it's an opaque type and requires some fiddling. In your stored procedure, just create an XMLType from the input CLOB via code like v_arg := XMLType(p_arg);

If your XML is not stored in a String, I don't know what to say since I tend to grab XML from a request and it comes out as a String.

Friday, March 28, 2008

Oracle REF Cursor

To use a cursor, you have to create a package. The convention is to use ref cursor for everything. If it doesn't exist, you can create it by running:


CREATE OR REPLACE PACKAGE types
AS
TYPE ref_cursor IS REF CURSOR;
END;


Then you can use the ref cursor like so;



CREATE OR REPLACE FUNCTION sp_get_stocks(v_price IN NUMBER)
RETURN types.ref_cursor
AS
stock_cursor types.ref_cursor;
BEGIN
OPEN stock_cursor FOR
SELECT ric,price,updated FROM stock_prices
WHERE price <>

Thursday, March 27, 2008

Counting the number of nodes in an XMLType

Newer Oracle versions can use a function called XMLTable, but <10.2 have to use another method:

select count(*) into number_of_items from table(xmlsequence((the_xml.extract('/*/mynode', 'xmlns="http://purl.org/rss/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"') )));

XMLType extract on XML with namespaces

Guess what, using extract('/mynode') in Oracle will not return mynode if the XML file has namespaces that Oracle doesn't recognize. The solution? use extract('/mynode','paste all your namespace declarations in here'). So for example:

extract('/mynode','xmlns="http://stupidideas.net/stupid" xmlns:dumb="http://dumb.com/dumber"')

See also:

http://pbarut.blogspot.com/2006/11/xmltype-and-namespace-problem.html

Tuesday, March 11, 2008

Saving and Loading an XMLType to/from Oracle from Java

First off, you will need ojdbc14.jar, xmlparsenv2.jar, and xdb.jar. You probably already have ojdbc14.jar if you are doing Oracle work, but the other two I had to download.


Lets get our imports going.


import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;

Now, you'll have to create a class, and inside it put this save function. My variable XMLdata is just a string I grabbed from the POST to this Java.


 public void save() {
Connection conn = null;
CallableStatement callableStatement = null;
ResultSet rs = null;
DataSource dataSource = null;
InitialContext ctx = null;
System.out.println("[save] Starting");
try {
ctx = new InitialContext();
dataSource = (DataSource)ctx.lookup("java:comp/env/yourdatasourcename");
conn = dataSource.getConnection();
System.out.println("[save] Fetching");
callableStatement = conn.prepareCall("{call SAVE(?,?,?,?)}");
System.out.println("[save] prepare call done, setting values");
callableStatement.setInt(1, var1);
callableStatement.setString(2, var2);
callableStatement.setString(3, var3);
callableStatement.setObject(4, XMLdata); // note that I use setObject instead
rs = callableStatement.executeQuery();
System.out.println("[save] Executed the query");

System.out.println("[save] inserted XML data: "+XMLdata);
rs.close();
callableStatement.close();
conn.close();
System.out.println("[save] Done");
} catch (SQLException e) {
System.out.println("[save] Saving the form blew up with an SQLException");
e.printStackTrace();
} catch (Exception e) {
System.out.println("[save] Saving the form blew up with an Exception");
e.printStackTrace();
}

}


The SAVE stored procedure just takes an integer, two varchar2s, and an XMLtype as input. Nothing exciting here. Let's move on to the trickier part: loading.



 public void load() {
Connection conn = null;
CallableStatement callableStatement = null;
ResultSet rs = null;
DataSource dataSource = null;
InitialContext ctx = null;
System.out.println("[load] Starting");
try {
ctx = new InitialContext();
System.out.println("got context");
dataSource = (DataSource)ctx.lookup("java:comp/env/yourdatasourcenamehere");
if(dataSource==null)
System.out.println("Got the datasource but it's null");
else
System.out.println("Got the datasource and it's not null");
conn = dataSource.getConnection();
System.out.println("Got the connection");
callableStatement = conn.prepareCall("{call LOAD(?,?,?,?)}");
System.out.println("[load] prepare call done, setting values");
callableStatement.setInt(1, InstanceId);
callableStatement.setString(2, ProcessName);
callableStatement.setString(3, TracingTag);
callableStatement.registerOutParameter(4, OracleTypes.OPAQUE,"SYS.XMLTYPE");
System.out.println("[load] Registered XMLType out parameter");
rs = callableStatement.executeQuery();
System.out.println("[load] Executed the query");
oracle.xdb.XMLType xml = oracle.xdb.XMLType.createXML((oracle.sql.OPAQUE)callableStatement.getObject(4));

System.out.println("[load] Got XML data: "+xml.getStringVal());
rs.close();
callableStatement.close();
conn.close();
System.out.println("[load] Done");
} catch (SQLException e) {
System.out.println("[load] Loading the form blew up with an SQLException");
e.printStackTrace();
} catch (Exception e) {
System.out.println("[load] Loading the form blew up with an Exception");
e.printStackTrace();
}

}

The key to loading is to first register an opaque out parameter. There are ways to use other types, but I found that subsequent manipulations of the returned data required starting with this type. You can get an XMLType, but then you need to use the connection in Oracle's libraries, and that breaks other stuff... so on. So use this: callableStatement.registerOutParameter(4, OracleTypes.OPAQUE,"SYS.XMLTYPE");


Opaque can store all sorts of Oracle types. To actually manipulate the XML in a useful way, you have to store it in an XMLType. To do that, create a new XMLType and then, using a cast, assign the opaque variable to it. Like so:


oracle.xdb.XMLType xml = oracle.xdb.XMLType.createXML((oracle.sql.OPAQUE)callableStatement.getObject(4));

That's the quick one line version. A little more readable is the following:


  Object xmlobject = callableStatement.getObject(4);
oracle.sql.OPAQUE xmlopaque= (oracle.sql.OPAQUE) xmlobject;
oracle.xdb.XMLType xml = oracle.xdb.XMLType.createXML(xmlopaque);

The nice thing about oracle.xdb.XMLType is that it behaves just like an XMLType in Oracle. So you can use the functions you're used to from PL/SQL. getStringVal() returns the string value of the XML for example.

Monday, October 29, 2007

Tomcat/Oracle CLOB gotcha?

Just spent two days troubleshooting why CLOB inserts were failing on one Tomcat install. It came down to two arguments in the resource declaration of my database in Tomcat:

accessToUnderlyingConnectionAllowed="true"
factory="org.apache.commons.dbcp.BasicDataSourceFactory"

The observed failure was that CLOB columns were set to NULL, even though the insert appeared to work. In one table, the CLOB column was declared as XMLTYPE NOT NULL, and inserts would fail with an XMLType error at "line 241". The line 241 the error mentioned was not in my code but in Oracle's.

Besides this, it was necessary to make sure commons-dbcp-1.2.2.jar and commons-pool-1.3.jar were in tomcat's common/lib directory, otherwise I got NoClassDefFound errors in catalinia.out.

Labels

Blog Archive

Contributors