package edu.edut.robin.utils;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.UnsupportedEncodingException;import java.security.MessageDigest;import android.util.Log;public class MD5Util{private static String encodingAlgorithm="MD5";public final static byte[] md5sum(byte[] data) {if(data == null || data.length == 0){return null;}try {MessageDigest mdTemp = MessageDigest.getInstance(encodingAlgorithm);mdTemp.update(data);return mdTemp.digest();} catch (Exception e) {}return null;}static String convertToHexString(byte data[]) {StringBuffer strBuffer = new StringBuffer();for (int i = 0; i < data.length; i++) {strBuffer.append(Integer.toHexString(0xff & data[i]));}return strBuffer.toString();}public static byte[] md5sum(File file) {InputStream fis = null;byte[] buffer = new byte[1024];int numRead = 0;MessageDigest md5;try{fis = new FileInputStream(file);md5 = MessageDigest.getInstance(encodingAlgorithm);while((numRead=fis.read(buffer)) > 0) {md5.update(buffer,0,numRead);}return md5.digest();} catch (Exception e) {e.printStackTrace();return null;} finally{if(fis != null){try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}}
Linode VPS 在业界有着较高的信誉评价,在国外和国内用户中的口碑不错,价格适中。Linode 提供很多了原生的系统(Linode嘛,Linux Node,都是Linux的,没有Windows的),装好基本的系统之后,可以根据自己的需求随便安装什么软件。并且,“重装系统”也非常的方便。这对新手来说,既不复杂上手,也容易将精力集中到学习Linux上。
独立IP, 支持众多linux发行版,root权限,想怎么折腾怎么折腾。 与一般主机的价格持平,但关键是它的服务器性能更加出色,所以性价比相对更高。
Linode 的机房Linode有6个机房可选:London、Newark、Atlanta、Dallas、Fremont、Tokyo,对于中国用户来说,选择东京机房会很快,并且服务器更新程序速度也超快。
Linode 新增的日本东京机房,速度相当给力。Linode 针对亚太地区用户的需求新开辟了日本东京机房。价格不变,速度会更快。 Linode Cloud Asia-Pacific!
如果创业团队要选择 VPS ,面向国内用户的话,建议选择 Linode 东京的机房就可以,访问速度相当不错。
使用招行信用卡就能付款。除了可以用来做Web服务器,Linode还可以用ssh跟vpn做代理翻墙。
VPS的优点在于可以按需购买,不必花费独立服务器的高昂费用;相比于Dreamhost的Shared Hosting,VPS有可以保证的资源(CPU、内存等),不会有over selling。
Linode的客服灰常给力,不管什么时间,用Ticket联系客服之后,一般半小时之内能够给你答复。基本上问题都能联系客服解决。
选择Linode的理由:- 对中国用户来说,Linode网速够快,即使跨越太平洋,速度依然出色;
- Linode自由度够大,有root权限,自己的主机完全由自己管理;
- Linode的服务够好,准确、及时、专业。值得鼎力推荐。
- Linode 是不是最好的 VPS ?不一定是,但可以肯定的是至少它是一个很不错的 VPS,这就够了。我看到有很多网友整天在各个 VPS 之前迁移来迁移去的,其实,人的时间也是很大的成本,也是要慎重考虑在内的。
- 完善的技术支持:除了可以在后台发送 Ticket 并及时得到回应外, Linode 还通过 Wiki, 论坛等方式提供了大量的技术文档及协助。
- 提供无缝升级功能:如果你有一天觉得现在的主机方案已不能满足需求,你可以直接在后台无缝升级到更高的主机方案,获取更多内存,存储空间及流量,而不必担心需要重装系统及数据的丢失。
- 强大的后台管理功能:这是 Linode 最具特色的功能之一,是我见过的自定义功能最强,自动化最完善的后台管理功能。它可以把存储空间划分成几个硬盘,用来安装不同的 Linux 发行版,然后你可以选择启用哪个硬盘作为线上服务器,同时也可以停机后启动别的硬盘,这样就无需重新安装系统就可以在不同的发行版之间切换,极为方便,而且这一切都不需要人工干预。后台还提供了详细的图形化的数据统计功能,包括 CPU 使用率,流量使用,IO 使用等等
- Linode 基于 iOs 还提供了 iPhone 版的后台管理工具,这样你可以在 iPhone 手机上直接管理你的 VPS 主机了。
1、登录 Linode 官网 ,http://www.linode.com/ 或者直接点击:http://www.linode.com/?r=616c3abf2f54a9fa8892a1be042fef6b1f52a5fa
2、选择适合你自己的套餐,点击购买。
我选择linode 1024这个套餐,进行下面的购买及支付需要一张支持美元的信用卡,VISA、MASTER Card都可以。国内大部分银行的都可以,我这里使用的招商银行的信用卡。
直接点击:http://www.linode.com/?r=616c3abf2f54a9fa8892a1be042fef6b1f52a5fa如果您觉得本文对您有用处,你可以在购买的时候在Referral
Code栏填写: "616c3abf2f54a9fa8892a1be042fef6b1f52a5fa"
如果有朋友看了我的文章觉得有用的话,恰好你又要购买 Linode 的 VPS,不妨使用我的 Refer 代码,于己无损,于人受益。
点击“continue”之后,回来到一个信息确认页面,有个协议需要确认,单击小方框,出现小勾就行了。
还有一个需要注意的是,好像不容易买到刚好一个月,比如我26号买的,这个月剩余的5天,我同样需要购买,实际上我支付了1个月15天,共计:23.82美元。事后,后台截图:
5、上步确认之后,你的Linode vps就购买完成了,手机上也收到了银行发来的信用卡支付信息,同时邮箱也受到订单的信息和帐号激活信息。
A:请看 Linode4个机房的速度比较(杭州电信)。测试方法为单线程在4个机房的服务器上分别下载91M的文件。
Linode 各大机房速度比较 机房 速度 Fremont, CA 140.90 KB/s Dallas, TX 8.33 KB/s Atlanta, GA 55.65 KB/s Newark, NJ 141.09 KB/s Tokyo, JP 245.20 KB/s可以看到,Fremont机房和Newark机房都是很好的选择。我的Linode选择了Fremont机房,可以在此处实际测试一下服务器的速度(测出的数值除以8就是相应的KB/sec)。
Linode官方专门建立了一个速度测试页面。一共5个机房(包括最新的London机房),每个机房都提供相同的100MB大小的测试文件以供下载。用浏览器随意测试了一下,对于国内用户来说仍然是Fremont和Dallas两个机房最快,对于网通线路来说Dallas甚至比大部分人推荐的Fremont机房更快一点。
注:最近也有朋友发现达拉斯机房的速度很不错,相比Fremont/Newark要好。所以建议大家购买时,自己实际测试下,选一个表现最好的。
Q:使用Linode如何进行备份? A:Linode自己提供了备份backup的服务,按照不同等级的Plan进行收费。如果数据确实很重要,备份到Amazon S3或者Google Storage也是个很好的选择。一个关键的优势在于:服务器放在国外机房,不管是连接S3或Google Storage都是飞快. Q:linode.com 如何扣费及扣费时间 A:linode的扣费政策在EST时间即美国东部时间早上进行扣费,EST时间与北京时间相差12个小时,比如我们北京时间现在是中午12点,而美国东部时间那里则是零点整 Q:Linode VPS 超流量的扣费处理 A:今天一朋友这个月刚找我代买的linode.com VPS 但被人DDOS 超了流量,超了一大半了.然后被扣了 6.6USD美元,1G扣费在0.15USD左右。 如果帐号里没钱估计就有可能被停机处理了。- 充分利用Linode服务器四核运算能力:榨干服务器,让进程运行在制定的CPU
- 简单快捷地备份Linode数据:备份数据库到S3或者Google Stroage
- 系统初装之后,建议进行一次LAMP最小优化
Linode前几天新开了Tokyo,JP机房,对于中国用户来说Tokyo机房的速度相较之前大部分人选择的Fremont,CA机房速度来说提升可不是一点点。根据我的测试,从Tokyo机房下载可以到1.2MB/s左右,而上传跑满了我的上行带宽5MB/s,ping值在100ms以下,不管是电信还是联通都是如此。
搬迁搬迁很简单,在Linode后台进入“Support”开一个新的Ticket,就说你要把VPS搬迁到Tokyo。一般半个小时以内客服就会回信告知你已经准备完毕,同时提供给你新的IP地址。只需要去后台关闭VPS,然后点击大大的搬迁按钮就开始搬迁了。整个搬迁过程1-5小时不等。
Tip: 最好在搬迁之前记下原有的IP地址,如果你同时开启了内网IP也请一并记下。
修改域名DNS搬迁过程中可以先修改域名的DNS指向,如果使用Linode的DNS,只需要在后台修改“DNS Manager”中相关项目。
修改配置搬迁完毕后进入Linode后台,查看“Remote Access”下的“Lish via SSH”项目,使用这里提供的地址登陆VPS。搬迁后所要修改的基本上就是原有配置中的IP地址,这里提供一条命令来快速修改:
# cd /etc # find ./ -exec grep old.x.x.x ‘{}’ \; -exec sed -i.bak s/old.x.x.x/new.x.x.x/g ‘{}’ \;此命令会替换掉旧IP地址,并且留下.bak的备份文件。 其中old.x.x.x是原有的IP地址,new.x.x.x是新的IP地址。需要修改的IP包括:Public IPs, Private IPs, Default Gateways, DNS Resolvers。 个人建议Public IPs和Private IPs使用以上命令替换,Default Gateways和DNS Resolvers最好手动修改以下两个配置文件:
/etc/network/interfaces /etc/resolv.conf 最后重启服务器,然后等待DNS设置同步完毕就可以使用了。
自打空间“搬出来”之后,换过的国外空间也不下10个:Godaddy、Dreamhost、Froghost、Photonvps等,本人没别的爱好,就喜欢瞎鼓捣,用了这么多,哪个都觉得不“完美”,多少都有点瑕疵,比如Ping值不稳定、路由线路不固定、主机上服务态度不好等等,不一而足。就这么断断续续晃了近一年,而且也因此在搜索引擎收录上吃了不少亏,感觉自己的空间就像个逃难的!
不过还好,最终碰上了Linode,第一感觉就是稳定,太稳定了:Ping值方面,电信线路一直稳定在220ms左右(河北电信),联通一直稳定在270ms左右(河北联通),铁通有点连蹦带跳,不过也在190~230ms;第二:路由线路稳定,由于选择的是HE的Fremont,CA的机房,所以,无论用国内的哪个宽带运营商,都走的是HE线路,从最后一个国内节点往后数到你的空间,不超过4个节点。第三:母机稳定,使用三个月以来,母机没重启过,就是自己爱折腾,中间重启VPS十几次,其余时间一切基本正常。Linode所宣称的在线率99.9%还真不是吹的。
在服务方面,Linode也做得细致入微:在Linode Library中,基本上囊括了VPS安装过程中的一切问题,而且分门别类,无论Centos,还是Ubuntu;从Apache到Lighttpd,Linode都将其中涉及到的问题归类,如果你的鸟语过了四级,基本上就用不到找客服,反正在这三个月内,我没找过客服,也没提交过任何问题。
另外,Linode还提供了免费域名DNS服务(NS1~NS5.LINODE.COM),分别位于美国的Houston、Nashville、Atlanta、Cedar Knolls和英国,基本上算是一个“全球分布”的DNS,功能也非常全面,对于普通域名管理,绰绰有余。
package com.cn; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; //错误小结: //1 关于file.isFile()与file.isDirectory()记忆出了偏差. // 错误以为若是Directory一定是file,file不一定是Directory // 更正:file和Directory是两码事.只能是file或Directory中其中一个. // 要看的1 传入一个路径直接压缩了. //2 血的教训: // zip()方法中没有关闭流zos.导致压缩后的文件解压时出错 //重要总结: //1 关于zip和unzip的核心操作对象是每一个文件!!! // 比如碰到一个directory,那么会去遍历里面的每一个文件,挨个对其进行压缩. // 不要错误地理解,若是一个directory,那么会将其作为一个整体进行压缩. //2 在JAVA中每一个压缩文件都用一个ZipEntry表示 // 所以在压缩的过程中要得到每个文件的完整路径(从最外层文件夹至文件本身) // 用此完整路径为每个压缩文件new()一个ZipEntry //3 所以利用zipEntry可记录原来的目录层次.解压后才保持原样 // 也可以看到在解压时利用entrys.hasMoreElements()来挨个 // 解压每个zipEntry public class TestZipAndUnZip { public static void main(String[] args) throws Exception { TestZipAndUnZip test=new TestZipAndUnZip(); //将某一个文件压缩成zip // test.zip("E:\\", "aa\\1.txt", "E:\\cc1.zip"); //将某一个目录压缩成zip //test.zip("E:\\aa", "bb\\", "E:\\zz.zip");//right // // test.zip("E:\\", "aa", "E:\\zz678910.zip");//right // // //将某一个zip解压 // test.unZipFile("E:\\zz.zip", "E:\\zzzz"); /////////////////////以下正确/////////////////////////////// //压缩和解压单个文件 // test.zip2("E:\\aa\\1.txt","E:\\aaaaaaa\\aa1234.zip"); // test.unZipFile2("E:\\aaaaaaa\\aa1234.zip", "E:\\aaaaaaa\\aaxixihaha"); //压缩和解压一个目录 test.zip2("D:\\developmentTools","E:\\20.zip"); test.unZipFile2("E:\\20.zip", "E:\\555aaxx99xxx"); /////////////////////以上正确////////////////////////////// } /** * @param willZipDirPath 被压缩文件(目录)所在目录 * @param willZipFileName 被压缩文件(目录)的名称 * @param toFilePath 压缩后文件(目录)名 */ public void zip(String willZipDirPath, String willZipFileName, String zipedFileName) { System.out.println("…………………以下为zip()方法…………………………"); if (willZipDirPath == null) { return; } File willZipDir = new File(willZipDirPath); if (!willZipDir.exists() || !willZipDir.isDirectory()) { return; } // 获得目录绝对路径 String willZipDirAbsolutePath = willZipDir.getAbsolutePath(); System.out.println("willZipDir.getAbsolutePath()="+willZipDirAbsolutePath); //压缩后的文件 File zipedFile = new File(zipedFileName); try { //从压缩后的文件得到压缩输出流ZipOutputStream //这里的zos只指向了压缩后文件的最外层.那么它怎么 //维持了原来的目录结构呢? //就是利用了ZipEntry!!! //在fileToZip()方法中就可以看到ZipEntry的使用!! ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipedFile)); if (willZipFileName.equals("*")) { //若传入的是*表示将此路径下所有东西都要压缩 //所以调用dirToZip() dirToZip(willZipDirAbsolutePath, willZipDir, zos); } else { //将被压缩的文件 File willZipFile = new File(willZipDirPath, willZipFileName); System.out.println("ccccccccccc name="+willZipFile.getName()); System.out.println("ccccccccccc getAbsolutePath="+willZipFile.getAbsolutePath()); if (willZipFile.isFile()) { System.out.println("…………………最外层开始压缩文件………………………"); fileToZip(willZipDirPath, willZipFile, zos); System.out.println("…………………最外层压缩文件结束………………………"); } if (willZipFile.isDirectory()) { System.out.println("…………………最外层开始压缩目录………………………"); dirToZip(willZipDirPath, willZipFile, zos); System.out.println("…………………最外层压缩目录结束………………………"); } // 关闭流!!! zos.close(); System.out.println("…………………以上为zip()方法…………………………"); } } catch (Exception e) { // TODO: handle exception } } /** * @param dirPath 被压缩文件所在目录 * @param willZipFile 被压缩文件的名称 * @param zos 输出流 */ public void fileToZip(String dirPath, File willZipFile,ZipOutputStream zos){ FileInputStream fis=null; ZipEntry zipEntry=null; byte [] buffer=new byte[1024*8]; int len=0; if (willZipFile.isFile()) { try { fis=new FileInputStream(willZipFile); zipEntry=new ZipEntry(getEntryName(dirPath, willZipFile)); zos.putNextEntry(zipEntry); System.out.println("…………………以下为fileToZip()方法…………………………"); System.out.println("zipEntry.getName="+zipEntry.getName()); System.out.println("zipEntry.isDirectory="+zipEntry.isDirectory()); System.out.println("zipEntry.getSize="+zipEntry.getSize()); System.out.println("zipEntry.getTime="+zipEntry.getTime()); System.out.println("zipEntry.getComment="+zipEntry.getComment()); System.out.println("…………………以上为fileToZip()方法…………………………"); while((len=fis.read(buffer))!=-1){ zos.write(buffer, 0, len); } zos.closeEntry(); fis.close(); } catch (Exception e) { } } } /** * @param dirPath 被压缩目录所在的上级目录 * @param willZipDir 被压缩目录 * @param zos 输出流 */ public void dirToZip(String dirPath, File willZipDir, ZipOutputStream zos) { if (willZipDir.isDirectory()) { File[] files = willZipDir.listFiles(); //处理-->该文件夹下无文件 if (files.length==0) { ZipEntry zipEntry=new ZipEntry(getEntryName(dirPath, willZipDir)); try { zos.putNextEntry(zipEntry); zos.closeEntry(); } catch (Exception e) { e.printStackTrace(); } return; } //处理-->该文件夹下的所有文件 for (int i = 0; i < files.length; i++) { File file = files[i]; //若是文件,递归调用fileToZip() if (file.isFile()) { System.out.println("xxxxxxxxxx内层开始fileToZip()方法xxxxxxxxxx"); fileToZip(dirPath, file, zos); System.out.println("xxxxxxxxxx内层fileToZip()方法结束xxxxxxxxxx"); } //若是文件,递归调用dirToZip() if (file.isDirectory()) { System.out.println("xxxxxxxxxx内层开始dirToZip()方法xxxxxxxxxx"); dirToZip(dirPath, file, zos); System.out.println("xxxxxxxxxx内层dirToZip()方法结束xxxxxxxxxx"); } } } } /** * @param dirPath 将被压缩文件所在目录 * @param willZipFile 将被压缩的文件 * @return */ //生成的是每个文件的完整路径(从最外层文件夹至文件本身) //这样生成的zipEntry就记录了原来的目录层次.解压后才保持原样 public String getEntryName(String dirPath, File willZipFile) { if (!dirPath.endsWith(File.separator)) { dirPath += File.separator; } String willZipFilePath=willZipFile.getAbsolutePath(); if (willZipFile.isDirectory()) { willZipFilePath+="/"; } int index=willZipFilePath.indexOf(dirPath); System.out.println("xx返回的 entryName="+ willZipFilePath.substring(index+dirPath.length())); return willZipFilePath.substring(index+dirPath.length()); } /** * @param zipedFileName 待解压zip文件 * @param unzipDirPath 文件解压后的最外层文件名 * @throws IOException */ public void unZipFile(String zipedFileName,String unzipDirPath) throws Exception{ if (!unzipDirPath.endsWith(File.separator)) { unzipDirPath+=File.separator; } try { ZipFile zipedFile=new ZipFile(zipedFileName); ZipEntry zipEntry=null; String entryName=null; String unzipedFileName=null; Enumeration entrys=zipedFile.entries(); byte [] buffer=new byte[1024*8]; int len=0; while (entrys.hasMoreElements()) { zipEntry=(ZipEntry) entrys.nextElement(); entryName=zipEntry.getName(); unzipedFileName=unzipDirPath+entryName; System.out.println("…………………以下为unZipFile()方法…………………………"); System.out.println("zipedFileName="+zipedFileName); System.out.println("unzipDirPath="+unzipDirPath); System.out.println("entryName="+entryName); System.out.println("unzipedFileName="+unzipedFileName); System.out.println("…………………以上为unZipFile()方法…………………………"); if (zipEntry.isDirectory()) { //没有执行此代码 new File(unzipedFileName).mkdirs(); } else { //总是执行该代码.因为压缩的时候是对每个文件进行压缩的. new File(unzipedFileName).getParentFile().mkdirs(); } File unzipedFile=new File(unzipedFileName); FileOutputStream fos=new FileOutputStream(unzipedFile); InputStream is=zipedFile.getInputStream(zipEntry); while ((len=is.read(buffer))!=-1) { fos.write(buffer, 0, len); } fos.close(); is.close(); } } catch (Exception e) { e.printStackTrace(); } } ///////////////////////////////////////////////////////////////////////////////////// /** * 该方法将一个给定路径的文件压缩 * @param willZipPath 将要压缩的文件的路径 * @param zipedPath 该文件压缩后的路径 */ public void zip2(String willZipPath,String zipedPath){ try { File willZipedFile=new File(willZipPath); File zipedFile=new File(zipedPath); ZipOutputStream zos=new ZipOutputStream(new FileOutputStream(zipedFile)); if (willZipedFile.isFile()) { fileToZip2(willZipPath,willZipedFile, zos); } if (willZipedFile.isDirectory()) { dirToZip2(willZipPath,willZipedFile, zos); } //方法调用完成后关闭流 zos.close(); } catch (Exception e) { // TODO: handle exception } } /** * @param rawPath * @param willZipedFile * @param zos */ //test.zip2("E:\\aa\\1.txt","E:\\aaaaaaa\\aa1234.zip"); public void fileToZip2(String rawPath,File willZipedFile,ZipOutputStream zos){ try { //实例化一个名称为ab.txt的ZipEntry对象 File file=new File(rawPath); ZipEntry entry = new ZipEntry(getEntryName2(rawPath, file)); //把生成的ZipEntry对象加入到压缩文件中 //而之后往压缩文件中写入的内容都会放在这个ZipEntry对象里面 zos.putNextEntry(entry); InputStream is = new FileInputStream(rawPath); int len = 0; while ((len = is.read()) != -1){ zos.write(len); } is.close(); //流关闭错误了!! //zos.close(); } catch (Exception e) { } } /** * @param rawPath 原文件所在路径 * @param zipedFile 压缩后的文件 * @param zos */ public void dirToZip2(String rawPath,File zipedFile, ZipOutputStream zos) { if (zipedFile.isDirectory()) { File[] files = zipedFile.listFiles(); //处理-->该文件夹下无文件 // if (files.length==0) { // //ZipEntry zipEntry=new ZipEntry(getEntryName(dirPath, willZipDir)); // ZipEntry zipEntry=new ZipEntry(""); // try { // zos.putNextEntry(zipEntry); // zos.closeEntry(); // } catch (Exception e) { // e.printStackTrace(); // } // return; // } for (int i = 0; i < files.length; i++) { File file = files[i]; //若是文件,递归调用fileToZip() if (file.isFile()) { fileToZip2(file.getAbsolutePath(),file, zos); } //若是文件,递归调用dirToZip() if (file.isDirectory()) { dirToZip2(file.getAbsolutePath(),file, zos); } } } } /** * @param rawPath 需要压缩的目录或者文件的完整路径 * @param file 需要压缩的文件 * @return entryName */ public String getEntryName2(String rawPath,File file){//file是rawPath对应的file try { String rawDir=rawPath.substring(3); int rawDirIndex=file.getAbsolutePath().indexOf(rawDir); String entryName=file.getAbsolutePath().substring(rawDirIndex); return entryName; } catch (Exception e) { } return null; } /** * @param zipedFilePath 原压缩文件的路径 * @param unzipPath 文件解压后的路径 */ public void unZipFile2(String zipedFilePath, String unzipPath){ try { //解压后的最外层文件夹 File unzipFile=new File(unzipPath); if (!unzipFile.exists()) { unzipFile.mkdirs(); } File zipedFile = new File(zipedFilePath); File perUnzipedFile = null; // 实例化ZipFile对象 这个很容易错!!!! // ZipFile zipFile = new ZipFile(file) ; ZipInputStream zis = new ZipInputStream(new FileInputStream(zipedFile)); FileOutputStream fos = null; ZipEntry entry = null; while ((entry = zis.getNextEntry()) != null) { // 得到一个压缩实体 perUnzipedFile = new File(unzipPath+File.separator+entry.getName()); if (!perUnzipedFile.getParentFile().exists()) { perUnzipedFile.getParentFile().mkdirs(); // 创建父目录 } if (!perUnzipedFile.exists()) { // 判断输出文件是否存在 perUnzipedFile.createNewFile(); // 创建文件 } // 得到每一个实体的输入流 fos = new FileOutputStream(perUnzipedFile); int len = 0; while ((len = zis.read()) != -1) { fos.write(len); } } zis.close(); fos.close(); } catch (Exception e) { // TODO: handle exception } } }