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