001    /****************************************************************
002     * Licensed to the Apache Software Foundation (ASF) under one   *
003     * or more contributor license agreements.  See the NOTICE file *
004     * distributed with this work for additional information        *
005     * regarding copyright ownership.  The ASF licenses this file   *
006     * to you under the Apache License, Version 2.0 (the            *
007     * "License"); you may not use this file except in compliance   *
008     * with the License.  You may obtain a copy of the License at   *
009     *                                                              *
010     *   http://www.apache.org/licenses/LICENSE-2.0                 *
011     *                                                              *
012     * Unless required by applicable law or agreed to in writing,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    package org.apache.james.rrt.hbase;
020    
021    import java.io.IOException;
022    import java.util.ArrayList;
023    import java.util.Collection;
024    import java.util.HashMap;
025    import java.util.List;
026    import java.util.Map;
027    
028    import org.apache.hadoop.hbase.KeyValue;
029    import org.apache.hadoop.hbase.client.Delete;
030    import org.apache.hadoop.hbase.client.Get;
031    import org.apache.hadoop.hbase.client.HTable;
032    import org.apache.hadoop.hbase.client.Put;
033    import org.apache.hadoop.hbase.client.Result;
034    import org.apache.hadoop.hbase.client.ResultScanner;
035    import org.apache.hadoop.hbase.client.Scan;
036    import org.apache.hadoop.hbase.util.Bytes;
037    import org.apache.james.rrt.api.RecipientRewriteTableException;
038    import org.apache.james.rrt.hbase.def.HRecipientRewriteTable;
039    import org.apache.james.rrt.lib.AbstractRecipientRewriteTable;
040    import org.apache.james.rrt.lib.RecipientRewriteTableUtil;
041    import org.apache.james.system.hbase.TablePool;
042    import org.slf4j.Logger;
043    import org.slf4j.LoggerFactory;
044    
045    /**
046     * Implementation of the RecipientRewriteTable for a HBase persistence.
047     */
048    public class HBaseRecipientRewriteTable extends AbstractRecipientRewriteTable {
049    
050        /**
051         * The Logger.
052         */
053        private static Logger log = LoggerFactory.getLogger(HBaseRecipientRewriteTable.class.getName());
054        
055        private static final String ROW_SEPARATOR = "@";
056        
057        /**
058         * @see org.apache.james.rrt.lib.AbstractRecipientRewriteTable#addMappingInternal(String, String, String)
059         */
060        @Override
061        protected void addMappingInternal(String user, String domain, String mapping) throws RecipientRewriteTableException {
062            String fixedUser = getFixedUser(user);
063            String fixedDomain = getFixedDomain(domain);
064            Collection<String> map = getUserDomainMappings(fixedUser, fixedDomain);
065            if (map != null && map.size() != 0) {
066                map.add(mapping);
067                doUpdateMapping(fixedUser, fixedDomain, RecipientRewriteTableUtil.CollectionToMapping(map));
068            } else {
069                doAddMapping(fixedUser, fixedDomain, mapping);
070            }
071        }
072    
073        /**
074         * @see org.apache.james.rrt.lib.AbstractRecipientRewriteTable#getUserDomainMappingsInternal(String, String)
075         */
076        @Override
077        protected Collection<String> getUserDomainMappingsInternal(String user, String domain) throws RecipientRewriteTableException {
078            HTable table = null;
079            List<String> list = new ArrayList<String>();
080            try {
081                table = TablePool.getInstance().getRecipientRewriteTable();
082                // Optimize this to only make one call.
083                feedUserDomainMappingsList(table, user, domain, list);
084            } catch (IOException e) {
085                log.error("Error while getting user domain mapping in HBase", e);
086                throw new RecipientRewriteTableException("Error while getting user domain mapping in HBase", e);
087            } finally {
088                if (table != null) {
089                    try {
090                        TablePool.getInstance().putTable(table);
091                    } catch (IOException e) {
092                        // Do nothing, we can't get access to the HBaseSchema.
093                    }
094                }
095            }
096            return list;
097        }
098        
099        private void feedUserDomainMappingsList(HTable table, String user, String domain, Collection<String> list) throws IOException {
100            Get get = new Get(Bytes.toBytes(getRowKey(user, domain)));
101            Result result = table.get(get);
102            List<KeyValue> keyValues = result.getColumn(HRecipientRewriteTable.COLUMN_FAMILY_NAME, HRecipientRewriteTable.COLUMN.MAPPING);
103            if (keyValues.size() > 0) {
104                list.addAll(RecipientRewriteTableUtil.mappingToCollection(Bytes.toString(keyValues.get(0).getValue())));
105            }
106        }
107    
108        /**
109         * @see org.apache.james.rrt.lib.AbstractRecipientRewriteTable#getAllMappingsInternal()
110         */
111        @Override
112        protected Map<String, Collection<String>> getAllMappingsInternal() throws RecipientRewriteTableException {
113            HTable table = null;
114            ResultScanner resultScanner = null;
115            Map<String, Collection<String>> map = null;
116            try {
117                table = TablePool.getInstance().getRecipientRewriteTable();
118                Scan scan = new Scan();
119                scan.addFamily(HRecipientRewriteTable.COLUMN_FAMILY_NAME);
120                scan.setCaching(table.getScannerCaching() * 2);
121                resultScanner = table.getScanner(scan);
122                Result result = null;
123                while ((result = resultScanner.next()) != null) {
124                    List<KeyValue> keyValues = result.list();
125                    if (keyValues != null) {
126                        for (KeyValue keyValue: keyValues) {
127                            String email = Bytes.toString(keyValue.getRow());
128                            if (map == null) {
129                                map = new HashMap<String, Collection<String>>();
130                            }
131                            Collection<String> list = map.get(email);
132                            if (list == null) {
133                                list = new ArrayList<String>();
134                            }
135                            list.add(Bytes.toString(keyValue.getRow()));
136                            map.put(email, list);
137                        }
138                    }
139                }
140            } catch (IOException e) {
141                log.error("Error while getting all mapping from HBase", e);
142                throw new RecipientRewriteTableException("Error while getting all mappings from HBase", e);
143            } finally {
144                if (resultScanner != null) {
145                    resultScanner.close();
146                }
147                if (table != null) {
148                    try {
149                        TablePool.getInstance().putTable(table);
150                    } catch (IOException e) {
151                        // Do nothing, we can't get access to the HBaseSchema.
152                    }
153                }
154            }
155            return map;
156        }
157    
158        /**
159         * @see org.apache.james.rrt.lib.AbstractRecipientRewriteTable#mapAddressInternal(String, String)
160         */
161        @Override
162        protected String mapAddressInternal(String user, String domain) throws RecipientRewriteTableException {
163            HTable table = null;
164            String mappings = null;
165            try {
166                table = TablePool.getInstance().getRecipientRewriteTable();
167                mappings = getMapping(table, user, domain);
168                if (mappings == null) {
169                    mappings = getMapping(table, WILDCARD, domain);
170                }
171                if (mappings == null) {
172                    mappings = getMapping(table, user, WILDCARD);
173                }
174            } catch (IOException e) {
175                log.error("Error while mapping address in HBase", e);
176                throw new RecipientRewriteTableException("Error while mapping address in HBase", e);
177            } finally {
178                if (table != null) {
179                    try {
180                        TablePool.getInstance().putTable(table);
181                    } catch (IOException e) {
182                        // Do nothing, we can't get access to the HBaseSchema.
183                    }
184                }
185            }
186            return mappings;
187        }
188        
189        private String getMapping(HTable table, String user, String domain) throws IOException {
190            Get get = new Get(Bytes.toBytes(getRowKey(user, domain)));
191            Result result = table.get(get);
192            List<KeyValue> keyValues = result.getColumn(HRecipientRewriteTable.COLUMN_FAMILY_NAME, HRecipientRewriteTable.COLUMN.MAPPING);
193            if (keyValues.size() > 0) {
194                return Bytes.toString(keyValues.get(0).getValue());
195            }
196            return null;
197        }
198        
199        /**
200         * @see org.apache.james.rrt.lib.AbstractRecipientRewriteTable#removeMappingInternal(String, String, String)
201         */
202        @Override
203        protected void removeMappingInternal(String user, String domain, String mapping) throws RecipientRewriteTableException {
204            String fixedUser = getFixedUser(user);
205            String fixedDomain = getFixedDomain(domain);
206            Collection<String> map = getUserDomainMappings(fixedUser, fixedDomain);
207            if (map != null && map.size() > 1) {
208                map.remove(mapping);
209                doUpdateMapping(fixedUser, fixedDomain, RecipientRewriteTableUtil.CollectionToMapping(map));
210            } else {
211                doRemoveMapping(fixedUser, fixedDomain, mapping);
212            }
213        }
214    
215        /**
216         * Update the mapping for the given user and domain.
217         * For HBase, this is simply achieved delegating
218         * the work to the doAddMapping method.
219         * 
220         * @param user the user
221         * @param domain the domain
222         * @param mapping the mapping
223         * @throws RecipientRewriteTableException
224         */
225        private void doUpdateMapping(String user, String domain, String mapping) throws RecipientRewriteTableException {
226            doAddMapping(user, domain, mapping);
227        }
228    
229        /**
230         * Remove a mapping for the given user and domain.
231         * 
232         * @param user the user
233         * @param domain the domain
234         * @param mapping the mapping
235         * @throws RecipientRewriteTableException
236         */
237        private void doRemoveMapping(String user, String domain, String mapping) throws RecipientRewriteTableException {
238            HTable table = null;
239            try {
240                table = TablePool.getInstance().getRecipientRewriteTable();
241                Delete delete = new Delete(Bytes.toBytes(getRowKey(user, domain)));
242                table.delete(delete);
243                table.flushCommits();
244            } catch (IOException e) {
245                log.error("Error while removing mapping from HBase", e);
246                throw new RecipientRewriteTableException("Error while removing mapping from HBase", e);
247            } finally {
248                if (table != null) {
249                    try {
250                        TablePool.getInstance().putTable(table);
251                    } catch (IOException e) {
252                        // Do nothing, we can't get access to the HBaseSchema.
253                    }
254                }
255            }
256        }
257    
258        /**
259         * Add mapping for given user and domain
260         * 
261         * @param user the user
262         * @param domain the domain
263         * @param mapping the mapping
264         * @throws RecipientRewriteTableException
265         */
266        private void doAddMapping(String user, String domain, String mapping) throws RecipientRewriteTableException {
267            HTable table = null;
268            try {
269                table = TablePool.getInstance().getRecipientRewriteTable();
270                Put put = new Put(Bytes.toBytes(getRowKey(user, domain)));
271                put.add(HRecipientRewriteTable.COLUMN_FAMILY_NAME, HRecipientRewriteTable.COLUMN.MAPPING, Bytes.toBytes(mapping));
272                table.put(put);
273                table.flushCommits();
274            } catch (IOException e) {
275                log.error("Error while adding mapping in HBase", e);
276                throw new RecipientRewriteTableException("Error while adding mapping in HBase", e);
277            } finally {
278                if (table != null) {
279                    try {
280                        TablePool.getInstance().putTable(table);
281                    } catch (IOException e) {
282                        // Do nothing, we can't get access to the HBaseSchema.
283                    }
284                }
285            }
286        }
287        
288        /**
289         * Constructs a Key based on the user and domain.
290         * 
291         * @param user
292         * @param domain
293         * @return the key
294         */
295        private String getRowKey(String user, String domain) {
296            return user + ROW_SEPARATOR + domain;
297        }
298    
299    }