Fwd: Delete words in a specific increment Position with Lucene
Αρχικό Μήνυμα Θέμα: Delete words in a specific increment Position with Lucene Ημερομηνία: Tue, 07 Feb 2012 18:48:03 +0100 Από:Damerian Προς: java-user-subscr...@lucene.apache.org Greetings, I used Lucene to make a simple filter that recognizes main names (Two consequent words in a strem that both start with Capital letters and all the rest ar lowercase letters). My problem is that although i can join the words and correctlly input the newly created token in its proper position in that position i still have as a token the first main name i found . My question is whether i can somehow delete the content of a specified position increment before using it. Thank you in advance for your answer, I hope this list is for questions like this and i am not spamming right now, regards!
Access next token in a stream
Hello i want to implement my custom filter, my wuestion is quite simple but i cannot find a solution to it no matter how i try: How can i access the TermAttribute of the next token than the one i currently have in my stream? For example in the phrase "My name is James Bond" if let's say i am in the token [My], i would like to be able to check the TermAttribute of the following token [name] and fix my position increment accordingly. Thank you in advance! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org
Re: Access next token in a stream
Στις 9/2/2012 8:54 μμ, ο/η Steven A Rowe έγραψε: Hi Damerian, One way to handle your scenario is to hold on to the previous token, and only emit a token after you reach at least the second token (or at end-of-stream). Your incrementToken() method could look something like: 1. Get current attributes: input.incrementToken() 2. If previous token does not exist: 2a. Store current attributes as previous token (see AttributeSource#cloneAttributes) 2b. Get current attributes: input.incrementToken() 3. Check for& store conditions that will affect previous token's attributes 4. Store current attributes as next token (see AttributeSource#cloneAttributes) 5. Copy previous token into current attributes (see AttributeSource#copyTo); the target will be "this", which is an AttributeSource. 6. Make changes based on conditions found in step #3 above 7. set previous token = next token 8. return true (Everywhere I say "token" I mean "instance of AttributeSource".) The final token in the input stream will need special handling, as will single-token input streams. Good luck, Steve -----Original Message- From: Damerian [mailto:dameria...@gmail.com] Sent: Thursday, February 09, 2012 2:19 PM To: java-user@lucene.apache.org Subject: Access next token in a stream Hello i want to implement my custom filter, my wuestion is quite simple but i cannot find a solution to it no matter how i try: How can i access the TermAttribute of the next token than the one i currently have in my stream? For example in the phrase "My name is James Bond" if let's say i am in the token [My], i would like to be able to check the TermAttribute of the following token [name] and fix my position increment accordingly. Thank you in advance! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org Hi Steve, Thank you for your immediate reply. i will try your solution but i feel that it does not solve my case. What i am trying to make is a filter that joins together two terms/tokens that start with a capital letter (it is trying to find all the Names/Surnames and make them one token) so in my aforementioned example when i examine [James] even if i store the TermAttribute to a temporary token how can i check the next one [Bond] , to join them without actually emmiting (and therefore creating a term in my inverted index) that has [James] on its own. Thank you again for your insight and i would relly appreciate any other views on the matter. Regards, Damerian - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org
Re: Access next token in a stream
Στις 9/2/2012 10:51 μμ, ο/η Steven A Rowe έγραψε: Damerian, The technique I mentioned would work for you with a little tweaking: when you see consecutive capitalized tokens, then just set the CharTermAttribute to the joined tokens, and clear the previous token. Another idea: you could use ShingleFilter with min size = max size = 2, and then use a following Filter extending FilteringTokenFilter, with an accept() method that examines shingles and rejects ones that don't qualify, something like the following. (Notes: this is untested; I assume you will use the default shingle token separator " "; and this filter will reject all non-shingle terms, so you won't get anything but names, even if you configure ShingleFilter to emit single tokens): public final class MyNameFilter extends FilteringTokenFilter { private static final Pattern NAME_PATTERN = Pattern.compile("\\p{Lu}\\S*(?:\\s\\p{Lu}\\S*)+"); private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class); @Override public boolean accept() throws IOException { return NAME_PATTERN.matcher(termAtt).matches(); } } Steve -----Original Message- From: Damerian [mailto:dameria...@gmail.com] Sent: Thursday, February 09, 2012 4:15 PM To: java-user@lucene.apache.org Subject: Re: Access next token in a stream Στις 9/2/2012 8:54 μμ, ο/η Steven A Rowe έγραψε: Hi Damerian, One way to handle your scenario is to hold on to the previous token, and only emit a token after you reach at least the second token (or at end-of- stream). Your incrementToken() method could look something like: 1. Get current attributes: input.incrementToken() 2. If previous token does not exist: 2a. Store current attributes as previous token (see AttributeSource#cloneAttributes) 2b. Get current attributes: input.incrementToken() 3. Check for& store conditions that will affect previous token's attributes 4. Store current attributes as next token (see AttributeSource#cloneAttributes) 5. Copy previous token into current attributes (see AttributeSource#copyTo); the target will be "this", which is an AttributeSource. 6. Make changes based on conditions found in step #3 above 7. set previous token = next token 8. return true (Everywhere I say "token" I mean "instance of AttributeSource".) The final token in the input stream will need special handling, as will single-token input streams. Good luck, Steve -Original Message- From: Damerian [mailto:dameria...@gmail.com] Sent: Thursday, February 09, 2012 2:19 PM To: java-user@lucene.apache.org Subject: Access next token in a stream Hello i want to implement my custom filter, my wuestion is quite simple but i cannot find a solution to it no matter how i try: How can i access the TermAttribute of the next token than the one i currently have in my stream? For example in the phrase "My name is James Bond" if let's say i am in the token [My], i would like to be able to check the TermAttribute of the following token [name] and fix my position increment accordingly. Thank you in advance! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org Hi Steve, Thank you for your immediate reply. i will try your solution but i feel that it does not solve my case. What i am trying to make is a filter that joins together two terms/tokens that start with a capital letter (it is trying to find all the Names/Surnames and make them one token) so in my aforementioned example when i examine [James] even if i store the TermAttribute to a temporary token how can i check the next one [Bond] , to join them without actually emmiting (and therefore creating a term in my inverted index) that has [James] on its own. Thank you again for your insight and i would relly appreciate any other views on the matter. Regards, Damerian - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org I think my solution in almost full now only one question you mentioned "clear the previous token. ". Is there a built-in method for doing that? In the begining i thought that if i put my new token into the same position increment it would "overwrite" the previous one , but what i succeeded was to simply inject code.. my method that does that so far is this: @Override public boolean incrementToken() throws IOException { if (!input.incrementToken()) { return false; } //Case were the previous token WAS NOT starting with capital letter and the rest small if (previousTokenCanditateMainName == false) { if (CheckIfMainName(termAtt.term()))
Re: Access next token in a stream
Στις 9/2/2012 11:12 μμ, ο/η Steven A Rowe έγραψε: Damerian, When I said "clear the previous token", I was referring to the pseudo-code I gave in my first response to you. There is no built-in method to do that. If you want to conditionally output tokens, you should store AttributeSource clones, as in my pseudo-code. Steve -Original Message----- From: Damerian [mailto:dameria...@gmail.com] Sent: Thursday, February 09, 2012 5:00 PM To: java-user@lucene.apache.org Subject: Re: Access next token in a stream Στις 9/2/2012 10:51 μμ, ο/η Steven A Rowe έγραψε: Damerian, The technique I mentioned would work for you with a little tweaking: when you see consecutive capitalized tokens, then just set the CharTermAttribute to the joined tokens, and clear the previous token. Another idea: you could use ShingleFilter with min size = max size = 2, and then use a following Filter extending FilteringTokenFilter, with an accept() method that examines shingles and rejects ones that don't qualify, something like the following. (Notes: this is untested; I assume you will use the default shingle token separator " "; and this filter will reject all non-shingle terms, so you won't get anything but names, even if you configure ShingleFilter to emit single tokens): public final class MyNameFilter extends FilteringTokenFilter { private static final Pattern NAME_PATTERN = Pattern.compile("\\p{Lu}\\S*(?:\\s\\p{Lu}\\S*)+"); private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class); @Override public boolean accept() throws IOException { return NAME_PATTERN.matcher(termAtt).matches(); } } Steve -Original Message- From: Damerian [mailto:dameria...@gmail.com] Sent: Thursday, February 09, 2012 4:15 PM To: java-user@lucene.apache.org Subject: Re: Access next token in a stream Στις 9/2/2012 8:54 μμ, ο/η Steven A Rowe έγραψε: Hi Damerian, One way to handle your scenario is to hold on to the previous token, and only emit a token after you reach at least the second token (or at end- of- stream). Your incrementToken() method could look something like: 1. Get current attributes: input.incrementToken() 2. If previous token does not exist: 2a. Store current attributes as previous token (see AttributeSource#cloneAttributes) 2b. Get current attributes: input.incrementToken() 3. Check for&store conditions that will affect previous token's attributes 4. Store current attributes as next token (see AttributeSource#cloneAttributes) 5. Copy previous token into current attributes (see AttributeSource#copyTo); the target will be "this", which is an AttributeSource. 6. Make changes based on conditions found in step #3 above 7. set previous token = next token 8. return true (Everywhere I say "token" I mean "instance of AttributeSource".) The final token in the input stream will need special handling, as will single-token input streams. Good luck, Steve -Original Message- From: Damerian [mailto:dameria...@gmail.com] Sent: Thursday, February 09, 2012 2:19 PM To: java-user@lucene.apache.org Subject: Access next token in a stream Hello i want to implement my custom filter, my wuestion is quite simple but i cannot find a solution to it no matter how i try: How can i access the TermAttribute of the next token than the one i currently have in my stream? For example in the phrase "My name is James Bond" if let's say i am in the token [My], i would like to be able to check the TermAttribute of the following token [name] and fix my position increment accordingly. Thank you in advance! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org Hi Steve, Thank you for your immediate reply. i will try your solution but i feel that it does not solve my case. What i am trying to make is a filter that joins together two terms/tokens that start with a capital letter (it is trying to find all the Names/Surnames and make them one token) so in my aforementioned example when i examine [James] even if i store the TermAttribute to a temporary token how can i check the next one [Bond] , to join them without actually emmiting (and therefore creating a term in my inverted index) that has [James] on its own. Thank you again for your insight and i would relly appreciate any other views on the matter. Regards, Damerian - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org I think my solution in almost full now only one question you mentioned "clear the previous token. ". Is there a built-in method for doing that? In the begining i thought that i
Implement a custom similarity
Hello, I am really new to Lucene, last week through this list i was really successfull into finding a solution to my problem. I have a new question now, i am trying to implement a new similarity class that uses the Jaccard coefficient, i have been reading the javadocs and a lot of other webpages on the matter, but my problem is that i still cannot understand how to do it. So far i know that i have to subclass the DefaultSimilarity and (if i am not wrong) i have to edit all the build in methods to return the corect score. Since Jaccard coefficiency is the conjuction of the query/document sets divided by the union of the two sets i think i only need the coord(q,d) and all the rest measures in the default similarity can return 1 to the score computation. My problem is that i cannot locate how to obtain the number of terms that each document has. Also do you think this approach is correct? I would be gratefull if you could give me advice or point towards a tutorial on the matter cause two days of searching were fruitless in finding an example code. Thank you in advance. - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org
Custom scoring
Hello, I am trying to implement my own Jaccard similarity for Lucene. So far i have the following code public class JaccardSimilarity extends DefaultSimilarity { int numberOfDocumentTerms; //String field="contents"; // Should the Jaccard similarity be only based in the contents field @Override public float idf(int i, int i1) { return 1; } @Override public float tf(int i) { return 1; } public int getNumberOfDocumentTerms() { return numberOfDocumentTerms; } public void setNumberOfDocumentTerms(int numberOfDocumentTerms) { this.numberOfDocumentTerms = numberOfDocumentTerms; } @Override public float queryNorm(float i) { return 1.0f; } @Override public float computeNorm(String field, FieldInvertState state) { numberOfDocumentTerms=state.getLength();//for each field we get the number of terms setNumberOfDocumentTerms(numberOfDocumentTerms); System.out.println("numberOfDocumentTerms from compute : " + numberOfDocumentTerms); return 1.0f; } @Override public float coord(int overlap, int maxOverlap) { System.out.println("numberOfDocumentTerms : " + getNumberOfDocumentTerms()); return (overlap/(numberOfDocumentTerms+(maxOverlap-overlap))); } } The problem is that coord() method is not used (or at least so that i understand) neither in searching nor in indexing What do i do wrong? i need the |overlap| - the number of query terms matched in the document |maxOverlap| - the total number of terms in the query to implement my scoring. Any help would be highly appreciated Thank you in advance!
QueryParser strange behavior
Hello! I have a small issue with the QueryParser in my program. It uses my custom filter to Parse its queries, but i get unexpexted results from when i am having an input from the keyboard To illustrate the code : Analyzer myAnalyzer = new ProperNameAnalyzer(); Query query = new QueryParser(Version.LUCENE_CURRENT, "content", myAnalyzer).parse("Jesus Christ"); //assertEquals(1, TestUtil.hitCount(searcher, query)); System.out.println("With ProperNameAnalyzer, Jesus Christ parses to " + query.toString("content")+ " query: " +query); will produce the following (expected ) output: With ProperNameAnalyzer, "Jesus Christ" parses to "Jesus Christ" query: contents:"Jesus Christ" Although with a small addition of keyboard iinteraction: BufferedReader in = null; String line = in.readLine(); Query query = new QueryParser(Version.LUCENE_CURRENT, "contents", analyzer).parse(line); System.out.println("With ProperNameAnalyzer, Jesus Christ parses to " + query.toString("contents")+ " query: " +query); Will produce the incorrect and unexpected output: With ProperNameAnalyzer, "Jesus Christ" parses to Jesus Christ query: contents:Jesus contents:Christ Any ideas why this may happen? Thanks in advance! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org
Re: QueryParser strange behavior
Στις 27/2/2012 11:45 πμ, ο/η Ian Lea έγραψε: Does your analyzer look for a field called content, not contents? -- Ian. On Sat, Feb 25, 2012 at 6:37 AM, Damerian wrote: Hello! I have a small issue with the QueryParser in my program. It uses my custom filter to Parse its queries, but i get unexpexted results from when i am having an input from the keyboard To illustrate the code : Analyzer myAnalyzer = new ProperNameAnalyzer(); Query query = new QueryParser(Version.LUCENE_CURRENT, "content", myAnalyzer).parse("Jesus Christ"); //assertEquals(1, TestUtil.hitCount(searcher, query)); System.out.println("With ProperNameAnalyzer, Jesus Christ parses to " + query.toString("content")+ " query: " +query); will produce the following (expected ) output: With ProperNameAnalyzer, "Jesus Christ" parses to "Jesus Christ" query: contents:"Jesus Christ" Although with a small addition of keyboard iinteraction: BufferedReader in = null; String line = in.readLine(); Query query = new QueryParser(Version.LUCENE_CURRENT, "contents", analyzer).parse(line); System.out.println("With ProperNameAnalyzer, Jesus Christ parses to " + query.toString("contents")+ " query: " +query); Will produce the incorrect and unexpected output: With ProperNameAnalyzer, "Jesus Christ" parses to Jesus Christ query: contents:Jesus contents:Christ Any ideas why this may happen? Thanks in advance! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org Thanks for the reply! No that's not the case... It was a typographic mistake here cause i took the code from my demo program (the one i use to test the code) i have the same name for the fields in both cases (hard coded and use input) regards! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org
Re: QueryParser strange behavior
Hello again! First of all thank you again for replying my amateur questions. I would like to rephrase my question because now what i described is not the case and its not a problem of input methods. I have made my custom analyzer which when indexing e.g the phrase "The quick Brown Fox" will produce the following tokens [The] [quick] [Brown Fox] when i use exactly the same analyser to construct a search query i get the following result: "With ProperNameAnalyzerThe quick Brown Fox parses to The quick Brown Fox query: contents:The contents:quick contents:Brown contents:Fox" which means that the analyzer fails to combine Brown and Fox into one token and make it a single term for the search as expected. Any insights on this? Once again thank you for your time and patience. Στις 28/2/2012 11:51 πμ, ο/η Ian Lea έγραψε: Then I don't know. Something trivial like white space? What does line.equals("Jesus Christ") say? -- Ian. On Mon, Feb 27, 2012 at 7:42 PM, Damerian wrote: Στις 27/2/2012 11:45 πμ, ο/η Ian Lea έγραψε: Does your analyzer look for a field called content, not contents? -- Ian. On Sat, Feb 25, 2012 at 6:37 AM, Damerianwrote: Hello! I have a small issue with the QueryParser in my program. It uses my custom filter to Parse its queries, but i get unexpexted results from when i am having an input from the keyboard To illustrate the code : Analyzer myAnalyzer = new ProperNameAnalyzer(); Query query = new QueryParser(Version.LUCENE_CURRENT, "content", myAnalyzer).parse("Jesus Christ"); //assertEquals(1, TestUtil.hitCount(searcher, query)); System.out.println("With ProperNameAnalyzer, Jesus Christ parses to " + query.toString("content")+ " query: " +query); will produce the following (expected ) output: With ProperNameAnalyzer, "Jesus Christ" parses to "Jesus Christ" query: contents:"Jesus Christ" Although with a small addition of keyboard iinteraction: BufferedReader in = null; String line = in.readLine(); Query query = new QueryParser(Version.LUCENE_CURRENT, "contents", analyzer).parse(line); System.out.println("With ProperNameAnalyzer, Jesus Christ parses to " + query.toString("contents")+ " query: " +query); Will produce the incorrect and unexpected output: With ProperNameAnalyzer, "Jesus Christ" parses to Jesus Christ query: contents:Jesus contents:Christ Any ideas why this may happen? Thanks in advance! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org Thanks for the reply! No that's not the case... It was a typographic mistake here cause i took the code from my demo program (the one i use to test the code) i have the same name for the fields in both cases (hard coded and use input) regards! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org
Re: QueryParser strange behavior
Στις 1/3/2012 3:08 μμ, ο/η Ian Lea έγραψε: Not a clue. I suggest you post a small, complete and self-contained (no external dependencies) program or test case that demonstrates the problem. And your analyzer. -- Ian. 2012/3/1 Damerian: Hello again! First of all thank you again for replying my amateur questions. I would like to rephrase my question because now what i described is not the case and its not a problem of input methods. I have made my custom analyzer which when indexing e.g the phrase "The quick Brown Fox" will produce the following tokens [The] [quick] [Brown Fox] when i use exactly the same analyser to construct a search query i get the following result: "With ProperNameAnalyzerThe quick Brown Fox parses to The quick Brown Fox query: contents:The contents:quick contents:Brown contents:Fox" which means that the analyzer fails to combine Brown and Fox into one token and make it a single term for the search as expected. Any insights on this? Once again thank you for your time and patience. Στις 28/2/2012 11:51 πμ, ο/η Ian Lea έγραψε: Then I don't know. Something trivial like white space? What does line.equals("Jesus Christ") say? -- Ian. On Mon, Feb 27, 2012 at 7:42 PM, Damerianwrote: Στις 27/2/2012 11:45 πμ, ο/η Ian Lea έγραψε: Does your analyzer look for a field called content, not contents? -- Ian. On Sat, Feb 25, 2012 at 6:37 AM, Damerian wrote: Hello! I have a small issue with the QueryParser in my program. It uses my custom filter to Parse its queries, but i get unexpexted results from when i am having an input from the keyboard To illustrate the code : Analyzer myAnalyzer = new ProperNameAnalyzer(); Query query = new QueryParser(Version.LUCENE_CURRENT, "content", myAnalyzer).parse("Jesus Christ"); //assertEquals(1, TestUtil.hitCount(searcher, query)); System.out.println("With ProperNameAnalyzer, Jesus Christ parses to " + query.toString("content")+ " query: " +query); will produce the following (expected ) output: With ProperNameAnalyzer, "Jesus Christ" parses to "Jesus Christ" query: contents:"Jesus Christ" Although with a small addition of keyboard iinteraction: BufferedReader in = null; String line = in.readLine(); Query query = new QueryParser(Version.LUCENE_CURRENT, "contents", analyzer).parse(line); System.out.println("With ProperNameAnalyzer, Jesus Christ parses to " + query.toString("contents")+ " query: " +query); Will produce the incorrect and unexpected output: With ProperNameAnalyzer, "Jesus Christ" parses to Jesus Christ query: contents:Jesus contents:Christ Any ideas why this may happen? Thanks in advance! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org Thanks for the reply! No that's not the case... It was a typographic mistake here cause i took the code from my demo program (the one i use to test the code) i have the same name for the fields in both cases (hard coded and use input) regards! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org Hi! Thanks for the immediate reply, i just fixed it though! apparently the QueryParser consructs a query regardless of the analyzer's tokens I simply created my own booleanQuery by calling my custom analyzer and treating the user inputed query text as a stream. The result works perfectly! Thank you again for your time and patience! :-) - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org
Pass arguments (numbers) to custom similarity
Hello, I am trying to implement my own custom similarity. My question is pretty simple, i know how to override the Similarity class and also to normalise the preexisting functions since they do not serve my purpose. How can i add an extra factor to the scoring formula and also how can i pass arguments that i will need (specifically termVectors) that are calculated in search time in my custom Similarity implementation. I hope my question is cler and easy to be answered. Thank you in advance! - To unsubscribe, e-mail: java-user-unsubscr...@lucene.apache.org For additional commands, e-mail: java-user-h...@lucene.apache.org