001package gu.sql2java.store;
002
003import static gu.sql2java.store.BinaryUtils.saveBytes;
004
005import java.io.File;
006import java.io.FileInputStream;
007import java.io.FileNotFoundException;
008import java.io.FilenameFilter;
009import java.io.IOException;
010import java.io.InputStream;
011import java.net.MalformedURLException;
012import java.net.URL;
013import java.net.URLConnection;
014import java.net.URLStreamHandler;
015import java.nio.file.Files;
016import java.nio.file.Path;
017import java.nio.file.Paths;
018
019/**
020 * 二进制数据本地存储实现
021 * @author guyadong
022 *
023 */
024public class LocalBinaryStore extends BaseURLStore {
025        private static final String ORIGIN_ROOT_NAME = "origin";
026        private static final String PROTOCOL = "lbs";
027        private final URLStreamHandler handler = new Handler(); 
028        private File storeRoot;
029        public static final LocalBinaryStore SINGLETON = new LocalBinaryStore();
030        private LocalBinaryStore() {
031        }
032
033        /**
034         * 创建iadb存储地址对象(将path中storeRoot路径剥离)
035         * @param file
036         * @return
037         */
038        protected URL createStoreURL(File file){
039                try {
040                        return new URL(PROTOCOL, null,file.toURI().toURL().getPath().substring(getStoreRoot().toURI().toURL().getPath().length()));
041                } catch (MalformedURLException e) {
042                        throw new RuntimeException(e);
043                }
044        }
045        @Override
046        protected boolean doExists(URL storedURL){
047                return Files.isReadable(pathOf(storedURL));
048        }
049        @Override
050        protected URL doFind(final String md5) {
051                File folder = relativeFolderPath(md5).toFile();
052                if(folder.isDirectory()){
053                        File[] files = folder.listFiles(new FilenameFilter() {
054                                @Override
055                                public boolean accept(File dir, String name) {
056                                        return name.startsWith(md5);
057                                }
058                        });
059                        try {
060                                return files.length >0 ? files[0].toURI().toURL() : null;
061                        } catch (MalformedURLException e) {
062                                throw new RuntimeException(e);
063                        }
064                }
065                return null;
066        }
067
068        protected Path pathOf(URL storedURL) {
069                // 在path中添加root路径
070                return Paths.get(getStoreRoot().getPath(),storedURL.getPath());
071        }
072        @Override
073        protected URL doStore(byte[] binary, String md5, String extension, boolean makeURLOnly) throws IOException {
074                File dst = createDestFile(md5, extension);
075                if(!makeURLOnly){
076                        if(dst.length() != binary.length){
077                                saveBytes(binary, dst, true);
078                        }                       
079                }
080                return createStoreURL(dst);
081        }
082        @Override
083        protected boolean doDelete(URL storedURL) throws IOException{           
084                return Files.deleteIfExists(pathOf(storedURL));
085        }
086
087        @Override
088        public final String getProtocol() {
089                return PROTOCOL;
090        }
091
092        public LocalBinaryStore setStoreRoot(File storeRoot) {
093                this.storeRoot = storeRoot;
094                return this;
095        }
096
097        private File getStoreRoot() {
098                return ConditionChecks.checkNotNull(storeRoot, 
099                                IllegalStateException.class, 
100                                "storeRoot is uninitialized,please call setStoreRoot(File) firstly");
101        }
102
103        protected String relativeFilePath(String md5, String suffix){
104                // md5前两位做第一级子目录,接下来的两位做第二级子目录
105                String s = (suffix == null || suffix.length() == 0) ? "" : "." + suffix;
106                Path p = Paths.get(ORIGIN_ROOT_NAME, md5.substring(0, 2),md5.substring(2, 4),md5 + s);
107                return  p.toString();
108        }
109        protected Path relativeFolderPath(String md5){
110                // md5前两位做第一级子目录,接下来的两位做第二级子目录
111                return Paths.get(ORIGIN_ROOT_NAME, md5.substring(0, 2),md5.substring(2, 4));
112        }
113        private File createDestFile(String md5, String suffix){
114                return new File(getStoreRoot(),relativeFilePath(md5, suffix));
115        }
116
117        @Override
118        protected URLStreamHandler doGetURLStreamHandler() {
119                return handler;
120        }
121        
122        @Override
123        public int hashCode() {
124                final int prime = 31;
125                int result = super.hashCode();
126                result = prime * result + ((getProtocol() == null) ? 0 : getProtocol().hashCode());
127                result = prime * result + ((storeRoot == null) ? 0 : storeRoot.hashCode());
128                return result;
129        }
130
131        @Override
132        public boolean equals(Object obj) {
133                if (this == obj)
134                        return true;
135                if (!super.equals(obj))
136                        return false;
137                if (!(obj instanceof LocalBinaryStore))
138                        return false;
139                LocalBinaryStore other = (LocalBinaryStore) obj;
140                if (getProtocol() == null) {
141                        if (other.getProtocol() != null)
142                                return false;
143                } else if (!getProtocol().equals(other.getProtocol()))
144                        return false;
145                if (storeRoot == null) {
146                        if (other.storeRoot != null)
147                                return false;
148                } else if (!storeRoot.equals(other.storeRoot))
149                        return false;
150                return true;
151        }
152
153        @Override
154        public String toString() {
155                StringBuilder builder = new StringBuilder();
156                builder.append("LocalBinaryStore [protocol=");
157                builder.append(getProtocol());
158                builder.append(",storeRoot=");
159                builder.append(storeRoot);
160                builder.append("]");
161                return builder.toString();
162        }
163
164        private class Handler extends URLStreamHandler {
165                @Override
166                protected URLConnection openConnection(URL u) throws IOException {
167                        return new FileConnection(u);
168                }
169        }
170        
171        private class FileConnection extends URLConnection{
172
173                protected FileConnection(URL url) {
174                        super(url);
175                }
176
177                @Override
178                public void connect() throws IOException {
179                        connected = true;
180                }
181
182                @Override
183                public InputStream getInputStream() throws IOException {
184                        connect();
185                        try {
186                                return new FileInputStream(pathOf(url).toFile());
187                        } catch (FileNotFoundException e) {
188                                throw new DataNotFoundException(url,e);
189                        }
190                }
191                
192        }
193}