001package gu.sql2java;
002
003import java.io.ByteArrayInputStream;
004import java.io.IOException;
005import java.io.InputStream;
006import java.lang.reflect.InvocationTargetException;
007import java.net.MalformedURLException;
008import java.net.URL;
009import java.net.URLConnection;
010import java.net.URLStreamHandler;
011import java.nio.ByteBuffer;
012import java.util.Map;
013
014import com.google.common.base.ConditionChecks;
015import com.google.common.base.Strings;
016import com.google.common.base.Throwables;
017
018import gu.sql2java.exception.ObjectRetrievalException;
019import net.gdface.exception.DataNotFoundException;
020import net.gdface.url.BaseURLStore;
021import net.gdface.utils.FaceUtilits;
022
023import static com.google.common.base.URLParseChecks.checkURLParse;
024import static com.google.common.base.ConditionChecks.checkNotNull;
025import static com.google.common.base.ConditionChecks.checkTrue;
026
027import static com.google.common.base.Preconditions.*;
028
029import static net.gdface.utils.FaceUtilits.*;
030
031public class BaseColumnStore extends BaseURLStore {
032
033        private final Handler handler = new Handler();
034
035        protected final  String tablename;
036
037        protected final String storeColumn;
038
039        protected final TableManager<BaseBean> manager;
040
041        protected final RowMetaData metaData;
042
043        protected final Class<?> storeColumnType;
044
045        protected final String protocol;
046
047        protected final int extensionId;
048
049        protected final int mimeId;
050
051        protected final static String PKS = "PKS";
052        
053        public BaseColumnStore(String tablename,String storeColumn, String extensionColumn, String mimeColumn) {
054                this.tablename = tablename;
055                this.storeColumn = storeColumn;
056                this.manager = Managers.managerOf(tablename);
057                this.metaData = RowMetaData.getMetaData(tablename);
058                this.extensionId = metaData.columnIDOf(extensionColumn);
059                this.mimeId = metaData.columnIDOf(mimeColumn);
060                this.storeColumnType = metaData.columnTypeOf(storeColumn);
061                checkArgument(storeColumnType != null, "INVALID column %s", storeColumn);
062                checkArgument(byte[].class.equals(storeColumnType) || ByteBuffer.class.isAssignableFrom(storeColumnType),
063                                "INVALID column type of %s,byte[] or java.nio.ByteBuffer required",storeColumn);
064                checkArgument(extensionColumn == null || String.class.equals(metaData.columnTypeOf(this.extensionId)),
065                                "INVALID extensionColumn %s",extensionColumn);
066                checkArgument(mimeColumn == null || String.class.equals(metaData.columnTypeOf(this.mimeId)),
067                                "INVALID mimeColumn %s",mimeColumn);
068                this.protocol = tablename.replaceAll("_", "-");
069        }
070
071        @Override
072        public String getProtocol() {
073                return protocol;
074        }
075        protected Object[] primaryKeysOf(String md5, String extension){
076                Map<String, Object> p = additionalParams.get();
077                checkState(p != null && p.containsKey(PKS), "NOT PK defined");
078                Object v = p.get(PKS);
079                checkState(v instanceof Object[],"INVALID PK type,Object[] required");
080                return (Object[])v;
081        }
082        protected final String pathOf(String suffix, Object... primaryKeys){
083                StringBuffer buffer = new StringBuffer(storeColumn);
084                for(int i=0; i < primaryKeys.length;++i){
085                        buffer.append("/").append(primaryKeys[i]);
086                }
087                if(!Strings.isNullOrEmpty(suffix)){
088                        buffer.append('.').append(suffix);
089                }
090                return buffer.toString();
091        }
092        protected final URL makeURL(String suffix, Object... primaryKeys){
093                try {
094                        return new URL(getProtocol(),null,-1,pathOf(suffix,primaryKeys));
095                } catch (MalformedURLException e) {
096                        throw new RuntimeException(e);
097                }
098        }
099        /**
100         * 从数据库记录中获取当前数据的扩展名
101         * @param bean 数据库记录(不为{@code null})
102         * @return 扩展名
103         */
104        protected String getExtension(BaseBean bean) {
105                return bean.getValue(extensionId);
106        }
107
108        /**
109         * 从数据库记录中获取当前数据的MIME类型
110         * @param bean 数据库记录(不为{@code null})
111         * @return MIME类型
112         */
113        protected String getMime(BaseBean bean) {
114                String mime = bean.getValue(mimeId);
115                String extension = bean.getValue(extensionId);
116                if(mime != null){
117                        return mime;
118                } else if( extension != null){
119                        mime = URLConnection.guessContentTypeFromName("." + extension);
120                        if(mime != null){
121                                return mime;
122                        }
123                }
124                return guessContentType(bean);
125        }
126
127        private String guessContentType(BaseBean bean) {
128                try {
129                        if(bean == null){
130                                return null;
131                        }
132                        byte[] data = FaceUtilits.getBytesOrNull(bean.getValue(storeColumn));
133                        if(data == null){
134                                return null;
135                        }
136                        ByteArrayInputStream is = new ByteArrayInputStream(data);
137                        return URLConnection.guessContentTypeFromStream(is);
138                } catch (IOException e) {
139                        throw new RuntimeException(e);
140                }
141        }
142
143        protected final void fillStoreBean(BaseBean bean, byte[] binary, String extension) {
144                if(ByteBuffer.class.equals(storeColumnType)){
145                        bean.setValue(storeColumn, ByteBuffer.wrap(binary));
146                }else{
147                        bean.setValue(storeColumn, binary);     
148                }
149                if(extensionId >= 0){
150                        bean.setValue(extensionId, extension);
151                }
152                if(mimeId >= 0){
153                        bean.setValue(mimeId, getMime(bean));
154                }
155        }
156
157        
158        @Override
159        protected URL doStore(byte[] binary, String md5, String extension, boolean makeURLOnly) throws IOException {
160                try {
161                        Object[] pks = primaryKeysOf(md5,extension);
162                        BaseBean bean = manager.loadByPrimaryKeyChecked(pks);
163                        fillStoreBean(bean, binary, extension);
164                        if(!makeURLOnly){
165                                manager.save(bean);
166                        }
167                        return makeURL(extension, pks);
168                } catch (Exception e) {
169                        Throwables.throwIfInstanceOf(e, IOException.class);
170                        throw new IOException(e);
171                } finally{
172                        additionalParams.remove();
173                }
174        }
175
176        @Override
177        protected URL doFind(String md5) {
178                return null;
179        }
180
181        @Override
182        protected boolean doExists(URL storedURL) {
183                DatabaseURLConnection connection = new DatabaseURLConnection(storedURL).parse();
184                BaseBean found = manager.loadByPrimaryKey(connection.primaryKeys);
185                return found != null && found.getValue(storeColumn) != null;
186        }
187
188        @Override
189        protected boolean doDelete(URL storedURL) throws IOException {
190                try {
191                        DatabaseURLConnection bean = new DatabaseURLConnection(storedURL).parse();
192                        return manager.deleteByPrimaryKey(bean.primaryKeys) == 1;
193                } catch (Exception e) {
194                        Throwables.throwIfInstanceOf(e, IOException.class);
195                        throw new IOException(e);
196                }
197        }
198
199        @Override
200        protected URLStreamHandler doGetURLStreamHandler() {
201                return handler;
202        }
203
204        @Override
205        public int hashCode() {
206                final int prime = 31;
207                int result = super.hashCode();
208                result = prime * result + extensionId;
209                result = prime * result + mimeId;
210                result = prime * result + ((protocol == null) ? 0 : protocol.hashCode());
211                result = prime * result + ((storeColumn == null) ? 0 : storeColumn.hashCode());
212                result = prime * result + ((tablename == null) ? 0 : tablename.hashCode());
213                return result;
214        }
215
216        @Override
217        public boolean equals(Object obj) {
218                if (this == obj)
219                        return true;
220                if (!super.equals(obj))
221                        return false;
222                if (!(obj instanceof BaseColumnStore))
223                        return false;
224                BaseColumnStore other = (BaseColumnStore) obj;
225                if (extensionId != other.extensionId)
226                        return false;
227                if (mimeId != other.mimeId)
228                        return false;
229                if (protocol == null) {
230                        if (other.protocol != null)
231                                return false;
232                } else if (!protocol.equals(other.protocol))
233                        return false;
234                if (storeColumn == null) {
235                        if (other.storeColumn != null)
236                                return false;
237                } else if (!storeColumn.equals(other.storeColumn))
238                        return false;
239                if (tablename == null) {
240                        if (other.tablename != null)
241                                return false;
242                } else if (!tablename.equals(other.tablename))
243                        return false;
244                return true;
245        }
246
247        @Override
248        public String toString() {
249                StringBuilder builder = new StringBuilder();
250                builder.append(getClass().getSimpleName() + " [tablename=");
251                builder.append(tablename);
252                builder.append(", storeColumn=");
253                builder.append(storeColumn);
254                builder.append(", extensionColumn=");
255                builder.append(metaData.columnNameOf(extensionId));
256                builder.append(", mimeColumn=");
257                builder.append(metaData.columnNameOf(mimeId));
258                builder.append(", protocol=");
259                builder.append(protocol);
260                builder.append("]");
261                return builder.toString();
262        }
263
264        protected class Handler extends URLStreamHandler{
265        
266                @Override
267                protected URLConnection openConnection(URL u) throws IOException {
268                                return new DatabaseURLConnection(u);    
269                }
270                
271        }
272        protected class DatabaseURLConnection extends URLConnection{
273                
274                Object[] primaryKeys;
275
276                protected DatabaseURLConnection(URL url) {
277                        super(url);
278                }
279                /**
280                 * 解析URL为数据访问信息<br>
281                 * 要求的URL格式 ${tablename}://${storeColumn}/${pk}...[/${pkN}]
282                 * @return
283                 * @throws URLParseException
284                 */
285                DatabaseURLConnection parse() throws URLParseException{
286                        checkTrue(getProtocol().equals(getURL().getProtocol()), URLParseException.class, 
287                                        "INVALID protocol ,%s reqired",getProtocol());
288                        String[] components = url.getPath().split("/");
289                        try {
290                                checkTrue(components[0].equals(storeColumn), URLParseException.class, 
291                                                "INVALID URL,the URL's path be reqired to start with '%s'",storeColumn);
292                                checkTrue(components.length == metaData.primaryKeyCount+1, URLParseException.class, 
293                                                "MISMATCH path components acount, %s required",metaData.primaryKeyCount+1);
294                                String last = components[components.length-1];
295                                int lastDot = last.lastIndexOf(".");
296                                if(lastDot >= 0){
297                                        // remove suffix if exists
298                                        components[components.length-1] = last.substring(0, lastDot);
299                                }                               
300                                primaryKeys = new Object[metaData.primaryKeyTypes.length];
301                                for(int i = 0; i < metaData.primaryKeyTypes.length;++i){
302                                        primaryKeys[i] = valueOf(components[i+1], metaData.primaryKeyTypes[i]);
303                                }
304                        } catch (IndexOutOfBoundsException e) {
305                                throw new URLParseException("URL's path lack the necessary components /${storeColumn}/${pk}...[/${pkN}]", e);
306                        } catch (StringCastException e) {
307                                throw new URLParseException("FAIL TO get primary key from URL's path", e);
308                        }
309                        checkURLParse(url != null, "NOT DEFINED store column name in ref(#)");
310                        Class<?> columnType = checkNotNull(metaData.columnTypeOf(storeColumn),URLParseException.class,"INVALID column %s",storeColumn);
311                        checkURLParse(byte[].class.equals(columnType) || ByteBuffer.class.isAssignableFrom(columnType),
312                                        "INVALID column type of %s, binary type ( byte[] or java.nio.ByteBuffer) required",storeColumn);
313                        return this;
314                }
315
316                @Override
317                public void connect() throws IOException {                      
318                        try {
319                                parse();
320                                connected = true;
321                        } catch (Exception e) {
322                                Throwables.throwIfInstanceOf(e, IOException.class);
323                                throw new IOException(e);
324                        }                       
325                }
326                @Override
327        public InputStream getInputStream() throws IOException{
328            connect();
329                        try {
330                                BaseBean bean = manager.loadByPrimaryKeyChecked(primaryKeys);
331                                Object data = bean.getValue(storeColumn);
332                                ConditionChecks.checkTrue(data != null, DataNotFoundException.class, "column %s of %s is null", storeColumn, url.toString());
333                                return new ByteArrayInputStream(getBytes(data));
334                        } catch (ObjectRetrievalException e) {
335                                throw new DataNotFoundException(url,e);
336                        }catch (Exception e) {
337                                Throwables.throwIfInstanceOf(e, IOException.class);
338                                throw new IOException(e);
339                        }               
340        }
341        }
342        @SuppressWarnings("unchecked")
343        private static <T> T valueOf(String input,Class<T> targetType)throws StringCastException{
344                        if(Strings.isNullOrEmpty(input) || String.class.equals(targetType)){
345                                return (T) input;
346                        }
347                        checkNotNull(targetType != null, StringCastException.class,"target is null");
348                        try {
349                                return (T) targetType.getMethod("valueOf", String.class).invoke(null, input);
350                        } catch (IllegalAccessException | NoSuchMethodException | SecurityException e) {
351                        } catch (InvocationTargetException e) {
352                                throw new StringCastException(e.getTargetException());
353                        }
354                        throw new StringCastException(String.format("INVALID target type %s",targetType));
355        }
356        public static class URLParseException extends RuntimeException{
357                private static final long serialVersionUID = 1L;
358
359                public URLParseException(Throwable cause) {
360                        super(cause);
361                }
362
363                public URLParseException(String message, Throwable cause) {
364                        super(message, cause);
365                }
366
367                public URLParseException(String message) {
368                        super(message);
369                }
370        }
371        public static class StringCastException extends RuntimeException{
372                private static final long serialVersionUID = 1L;
373
374                public StringCastException(Throwable cause) {
375                        super(cause);
376                }
377
378                public StringCastException(String message) {
379                        super(message);
380                }
381        }
382}
383