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