@SuppressWarnings("unchecked")
public class DataSourceManager {
	private static Class<TvDataSource> dsClass = null;
	public final static String RES_KEY_DS_CLASS_NAME = "ds_class_name";
	public final static String RES_KEY_DB_DRIVER_CLASS = "db_driver_class";
	public final static String RES_KEY_DB_URL = "db_url";
	public final static String RES_KEY_DB_USER_NAME = "db_username";
	public final static String RES_KEY_DB_PASSWORD = "db_password";
	public final static String RES_KEY_DB_FILE_NAME = "db_file_name";
	public final static String RES_KEY_DB_SQL_LIST = "db_sql_stmt";
	// 数据源配置文件前缀，如db_sqlite、db_mysql
	private final static String DB_PROPERTY_FILE_PREFIX = "db_";
	// 配置文件中的sql语句前缀
	private final static String DB_PROPERTY_FILE_SQL_PREFIX = "sql_";
	// DBCP连接池配置
	private final static String DBCP_FILE_NAME = "dbcp.properties";
	private final static String DBCP_URL = "url";
	private final static String DBCP_USER_NAME = "username";
	private final static String DBCP_PASSWORD = "password";
	private final static String DBCP_DRIVER_CLASS_NAME = "driverClassName";

	public final static Properties DATA_SOURCE_PROP = new Properties();
	private final static Properties DBCP_PROP = new Properties();
	private final static Pattern PATTERN_DB_PROPERTY_FILE_SQL_SUFFIX = Pattern
			.compile("_(\\d+)$");

	static {
		// dbcp
		InputStream is = DataSourceManager.class.getResourceAsStream("/"
				+ DBCP_FILE_NAME);
		try {
			DBCP_PROP.load(is);
		} catch (IOException e) {
			throw new MyTvException("error occur while load properties file: "
					+ DBCP_FILE_NAME, e);
		}

		// 非连接池
		ResourceBundle bundle = ResourceBundle
				.getBundle(DB_PROPERTY_FILE_PREFIX
						+ Config.getDbType().getValue());
		if (bundle.containsKey(RES_KEY_DS_CLASS_NAME)) {
			String dsClassName = bundle.getString(RES_KEY_DS_CLASS_NAME);
			try {
				dsClass = (Class<TvDataSource>) Class.forName(dsClassName);
			} catch (ClassNotFoundException e) {
				throw new MyTvException(dsClassName + " isn't found.", e);
			}
			DATA_SOURCE_PROP.setProperty(RES_KEY_DS_CLASS_NAME, dsClassName);
		}
		if (bundle.containsKey(RES_KEY_DB_DRIVER_CLASS)) {
			String dbDriverClass = bundle.getString(RES_KEY_DB_DRIVER_CLASS);
			try {
				Class.forName(dbDriverClass);
			} catch (ClassNotFoundException e) {
				throw new MyTvException(dbDriverClass + " isn't found.", e);
			}
			DATA_SOURCE_PROP
					.setProperty(RES_KEY_DB_DRIVER_CLASS, dbDriverClass);
			DBCP_PROP.setProperty(DBCP_DRIVER_CLASS_NAME, dbDriverClass);
		}
		if (bundle.containsKey(RES_KEY_DB_URL)) {
			String dbUrl = bundle.getString(RES_KEY_DB_URL);
			DATA_SOURCE_PROP.setProperty(RES_KEY_DB_URL, dbUrl);
			DBCP_PROP.setProperty(DBCP_URL, dbUrl);
		}
		if (bundle.containsKey(RES_KEY_DB_USER_NAME)) {
			String dbUserName = bundle.getString(RES_KEY_DB_USER_NAME);
			DATA_SOURCE_PROP.setProperty(RES_KEY_DB_USER_NAME, dbUserName);
			DBCP_PROP.setProperty(DBCP_USER_NAME, dbUserName);
		}
		if (bundle.containsKey(RES_KEY_DB_PASSWORD)) {
			String dbPassword = bundle.getString(RES_KEY_DB_PASSWORD);
			DATA_SOURCE_PROP.setProperty(RES_KEY_DB_PASSWORD, dbPassword);
			DBCP_PROP.setProperty(DBCP_PASSWORD, dbPassword);
		}
		if (bundle.containsKey(RES_KEY_DB_FILE_NAME)) {
			String dbFileName = bundle.getString(RES_KEY_DB_FILE_NAME);
			DATA_SOURCE_PROP.setProperty(RES_KEY_DB_FILE_NAME, dbFileName);
		}
		List<String> keyList = new ArrayList<String>(bundle.keySet());
		Collections.sort(keyList, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				int s1 = 0;
				int s2 = 0;
				Matcher matcher = PATTERN_DB_PROPERTY_FILE_SQL_SUFFIX
						.matcher(o1);
				if (matcher.find()) {
					s1 = Integer.valueOf(matcher.group(1));
				}
				matcher = PATTERN_DB_PROPERTY_FILE_SQL_SUFFIX.matcher(o2);
				if (matcher.find()) {
					s2 = Integer.valueOf(matcher.group(1));
				}
				return s1 - s2;
			}
		});
		List<String> sqlList = new ArrayList<String>();
		for (String key : keyList) {
			if (key.startsWith(DB_PROPERTY_FILE_SQL_PREFIX)) {
				String value = bundle.getString(key);
				sqlList.add(value);
			}
		}

		DATA_SOURCE_PROP.put(RES_KEY_DB_SQL_LIST, sqlList);

	}

	/**
	 * 获取数据源连接
	 * 
	 * @return
	 * @throws SQLException
	 */
	public static Connection getConnection() throws SQLException {
		if (dsClass == null) {
			throw new MyTvException("load db driver class error.");
		}

		try {
			if (Config.getDbType() == DbType.SQLITE) {
				return dsClass.newInstance().getConnection(DATA_SOURCE_PROP);
			}

			DataSource ds = BasicDataSourceFactory.createDataSource(DBCP_PROP);
			Connection conn = ds.getConnection();
			return conn;
		} catch (InstantiationException e) {
			throw new MyTvException("can't instantce db driver: " + dsClass, e);
		} catch (IllegalAccessException e) {
			throw new MyTvException(e);
		} catch (Exception e) {
			throw new MyTvException(e);
		}
	}
}
 public class CrawlEvent extends EventObject {
	private static final long serialVersionUID = -5496065547903238418L;

	public CrawlEvent(Object source) {
		super(source);
	}

}
 public interface CrawlEventListener {
	/**
	 * 抓取到电视台、电视节目对象的事件通知
	 * 
	 * @param event
	 */
	public void itemFound(CrawlEvent event);

	/**
	 * 抓取结束，即将返回结果
	 * 
	 * @param event
	 */
	public void crawlEnd(CrawlEvent event);
}
 public abstract class CrawlEventListenerAdapter implements CrawlEventListener {

	@Override
	public void itemFound(CrawlEvent event) {
	}

	@Override
	public void crawlEnd(CrawlEvent event) {
	}

}
 public class TvStationFoundEvent extends ItemFoundEvent<TvStation> {
	private static final long serialVersionUID = -4729651908093312009L;

	public TvStationFoundEvent(Object source, TvStation item) {
		super(source, item);
	}

}
 public class MyTvException extends RuntimeException {
	private static final long serialVersionUID = -2699920699817552410L;

	public MyTvException() {
		super();
	}

	public MyTvException(String message, Throwable cause) {
		super(message, cause);
	}

	public MyTvException(String message) {
		super(message);
	}

	public MyTvException(Throwable cause) {
		super(cause);
	}

}
 public class MyTv {
	private long id;
	private String displayName;
	private String stationName;
	private String classify;
	private String channel;
	private int sequence;

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getDisplayName() {
		return displayName;
	}

	public void setDisplayName(String displayName) {
		this.displayName = displayName;
	}

	public String getStationName() {
		return stationName;
	}

	public void setStationName(String stationName) {
		this.stationName = stationName;
	}

	public String getClassify() {
		return classify;
	}

	public void setClassify(String classify) {
		this.classify = classify;
	}

	public String getChannel() {
		return channel;
	}

	public void setChannel(String channel) {
		this.channel = channel;
	}

	public int getSequence() {
		return sequence;
	}

	public void setSequence(int sequence) {
		this.sequence = sequence;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((classify == null) ? 0 : classify.hashCode());
		result = prime * result
				+ ((displayName == null) ? 0 : displayName.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		MyTv other = (MyTv) obj;
		if (classify == null) {
			if (other.classify != null)
				return false;
		} else if (!classify.equals(other.classify))
			return false;
		if (displayName == null) {
			if (other.displayName != null)
				return false;
		} else if (!displayName.equals(other.displayName))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "MyTv [id=" + id + ", displayName=" + displayName
				+ ", stationName=" + stationName + ", classify=" + classify
				+ ", channel=" + channel + ", sequence=" + sequence + "]";
	}
}
 public class TvStation {
	private int id;
	private String name;
	private String city;
	private String classify;
	private int sequence;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public String getClassify() {
		return classify;
	}

	public void setClassify(String classify) {
		this.classify = classify;
	}

	public int getSequence() {
		return sequence;
	}

	public void setSequence(int sequence) {
		this.sequence = sequence;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((city == null) ? 0 : city.hashCode());
		result = prime * result
				+ ((classify == null) ? 0 : classify.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		TvStation other = (TvStation) obj;
		if (city == null) {
			if (other.city != null)
				return false;
		} else if (!city.equals(other.city))
			return false;
		if (classify == null) {
			if (other.classify != null)
				return false;
		} else if (!classify.equals(other.classify))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "TvStation [id=" + id + ", name=" + name + ", city=" + city
				+ ", classify=" + classify + ", sequence=" + sequence + "]";
	}

}
 public class ConfigProxy implements ProxyProvider {
	private final static Logger logger = LoggerFactory
			.getLogger(ConfigProxy.class);

	@Override
	public List<Proxy> getProxies() {
		List<Proxy> proxies = new ArrayList<Proxy>();
		ResourceBundle bundle = ResourceBundle.getBundle(Config
				.getConfigFileName());
		if (bundle.containsKey(Config.getResKeyConfigProxies())) {
			String value = bundle.getString(Config.getResKeyConfigProxies());
			if (!StringUtils.isEmpty(value)) {
				String[] values = value.split(Constant.COMMA);
				for (int i = 0, length = values == null ? 0 : values.length; i < length; i++) {
					String[] o = values[i].split(Constant.COLON);
					String ip = o[0];
					int port = Integer.valueOf(o[1]);
					Proxy proxy = new Proxy(ip, port);
					if (NetworkUtils.isReachable(ip, port, 10000)) {
						proxies.add(proxy);
						logger.info("find a proxy: " + proxy);
					} else {
						logger.info("proxy can't reachable. " + proxy);
					}
				}
			}
		}
		return proxies;
	}

}
 public class MyTvProxyManager {
	private final static Logger logger = LoggerFactory
			.getLogger(MyTvProxyManager.class);
	public final static String PROXY_ANONYMOUS_HIGH = "高匿";
	public final static String PROXY_TYPE_SOCK = "scok5";
	private final static String LOCALHOST = "localhost";
	private final ConcurrentLinkedQueue<Proxy> PROXY_QUEUE = new ConcurrentLinkedQueue<Proxy>();
	private final static Proxy LOCALHOST_PROXY = new Proxy(LOCALHOST, 80);
	private List<ProxyProvider> providerList = new ArrayList<ProxyProvider>();

	private MyTvProxyManager() {
	}

	private final static class MyTvProxySingletonHolder {
		private final static MyTvProxyManager MY_TV_PROXY = new MyTvProxyManager();
	}

	public static MyTvProxyManager getInstance() {
		return MyTvProxySingletonHolder.MY_TV_PROXY;
	}

	public void prepareProxies(ProxyProvider... providers) {
		int length = providers == null ? 0 : providers.length;
		if (length > 0) {
			int maxThreadNum = Constant.CPU_PROCESSOR_NUM;
			ThreadFactory threadFactory = new BasicThreadFactory.Builder()
					.namingPattern("MyTv_Find_Proxies_%d").build();
			ExecutorService executorService = Executors.newFixedThreadPool(
					length > maxThreadNum ? maxThreadNum : length,
					threadFactory);
			CompletionService<List<Proxy>> completionService = new ExecutorCompletionService<List<Proxy>>(
					executorService);
			providerList.clear();
			for (int i = 0; i < length; i++) {
				final ProxyProvider provider = providers[i];
				providerList.add(provider);
				completionService.submit(new Callable<List<Proxy>>() {

					@Override
					public List<Proxy> call() throws Exception {
						return provider.getProxies();
					}
				});
			}
			executorService.shutdown();

			int count = 0;
			List<Proxy> resultList = new ArrayList<Proxy>();
			while (count < length) {
				try {
					Future<List<Proxy>> future = completionService.take();
					List<Proxy> proxies = future.get();
					if (proxies != null) {
						resultList.addAll(proxies);
					}
				} catch (InterruptedException e) {
					logger.error("get proxies thread has interrupted.", e);
				} catch (ExecutionException e) {
					logger.error("get proxies thread has execution fail.", e);
				}
				count++;
			}
			resultList.add(LOCALHOST_PROXY);
			PROXY_QUEUE.clear();
			PROXY_QUEUE.addAll(resultList);
		}
	}

	/**
	 * 刷新代理服务器
	 */
	public void refresh() {
		int size = providerList.size();
		ProxyProvider[] providers = new ProxyProvider[size];
		prepareProxies(providerList.toArray(providers));
	}

	public int getProxySize() {
		return PROXY_QUEUE.size();
	}

	public Proxy pickProxy() {
		Proxy proxy = PROXY_QUEUE.poll();
		if (proxy == null) {
			return null;
		}
		PROXY_QUEUE.offer(proxy);
		if (LOCALHOST.equals(proxy.getIp())) {
			return null;
		}
		return proxy;
	}
}
 public interface TvService {
	/**
	 * 保存电视台
	 * 
	 * @param stations
	 */
	public void save(TvStation... stations);

	/**
	 * 保存电视节目表
	 * 
	 * @param programTables
	 */
	public void save(ProgramTable... programTables);

	/**
	 * 获取所有电视台分类
	 * 
	 * @return
	 */
	public List<String> getMyTvClassify();

	/**
	 * 获取所有可用于抓取的电视台对象列表
	 * 
	 * @return
	 */
	public List<TvStation> getAllCrawlableStation();

	public TvStation getStation(String stationName);

	/**
	 * 根据显示名称获取电视台对象
	 * 
	 * @param displayName
	 *            电视台显示名
	 * @param classify
	 *            电视台分类
	 * @return
	 */
	public TvStation getStationByDisplayName(String displayName, String classify);

	/**
	 * 根据电视台名称，日期获取电视节目表
	 * 
	 * @param stationName
	 *            电视台名称
	 * @param date
	 *            日期, yyyy-MM-dd
	 * @return
	 */
	public List<ProgramTable> getProgramTable(String stationName, String date);

	/**
	 * 根据电视台分类获取电视台
	 * 
	 * @param classify
	 * @return
	 */
	public List<MyTv> getMyTvByClassify(String classify);

	/**
	 * 判断指定电视台、日期的电视节目表是否已存在
	 * 
	 * @param stationName
	 *            电视台名称
	 * @param date
	 *            日期, yyyy-MM-dd
	 * @return
	 */
	public boolean isProgramTableExists(String stationName, String date);

	/**
	 * 抓取所有电视台
	 * 
	 * @param listeners
	 *            抓取事件监听器
	 * @return
	 */
	public List<TvStation> crawlAllTvStation(CrawlEventListener... listeners);

	/**
	 * 根据名称、日期抓取电视节目表
	 * 
	 * @param tvStation
	 *            电视台对象
	 * @param date
	 *            日期, yyyy-MM-dd
	 * @param listeners
	 *            抓取事件监听器
	 * @return
	 */
	public List<ProgramTable> crawlProgramTable(TvStation tvStation,
			String date, CrawlEventListener... listeners);

	/**
	 * 获取所有在客户端显示的电视台
	 * 
	 * @return
	 */
	public List<TvStation> getDisplayedTvStation();

	/**
	 * 获取所有我的电视台
	 * 
	 * @return
	 */
	public List<MyTv> getMyTv();

	/**
	 * 判断电视台是否是客户端显示的电视台
	 * 
	 * @param tvStation
	 * @return
	 */
	public boolean isInMyTv(TvStation tvStation);

}
 public class TvServiceImpl implements TvService {
	private TvDao tvDao = new TvDaoImpl();
	private final static int TV_STATION_PERSISTENT_THRESHOLD = 100;

	/**
	 * 保存电视台
	 * 
	 * @param stations
	 */
	@Override
	public void save(TvStation... stations) {
		int size = stations == null ? 0 : stations.length;
		if (size == 0) {
			return;
		}
		tvDao.save(stations);
	}

	/**
	 * 保存电视台节目表
	 * 
	 * @param programTables
	 */
	@Override
	public void save(ProgramTable... programTables) {
		int size = programTables == null ? 0 : programTables.length;
		if (size == 0) {
			return;
		}
		List<ProgramTable> resultList = new ArrayList<ProgramTable>();
		for (int i = 0; i < size; i++) {
			ProgramTable pt = programTables[i];
			String stationName = pt.getStationName();
			String date = pt.getAirDate();
			if (tvDao.isProgramTableExists(stationName, date)) {
				continue;
			}
			resultList.add(pt);
		}
		programTables = new ProgramTable[resultList.size()];
		tvDao.save(resultList.toArray(programTables));
	}

	/**
	 * 获取电视台分类
	 * 
	 * @return
	 */
	@Override
	public List<String> getMyTvClassify() {
		return tvDao.getMyTvClassify();
	}

	@Override
	public List<TvStation> getAllCrawlableStation() {
		List<TvStation> stationList = tvDao.getAllCrawlableStation();
		if (stationList != null) {
			MemoryCache.getInstance().addCache(stationList);
		}
		return stationList;
	}

	@Override
	public TvStation getStation(String stationName) {
		TvStation tvStation = MemoryCache.getInstance().getStation(stationName);
		if (tvStation == null) {
			List<TvStation> stationList = tvDao.getStation(stationName);
			if (stationList.size() > 0) {
				tvStation = stationList.get(0);
				MemoryCache.getInstance().addCache(tvStation);
			}
		}
		return tvStation;
	}

	/**
	 * 根据显示名取得电视台对象
	 * 
	 * @param displayName
	 *            电视台显示名
	 * @param classify
	 *            电视台分类，可以为null。为空时，将查找stationName与displayName相同的电视台
	 * @return
	 */
	@Override
	public TvStation getStationByDisplayName(String displayName, String classify) {
		TvStation tvStation = tvDao.getStationByDisplayName(displayName,
				classify);
		if (tvStation != null) {
			MemoryCache.getInstance().addCache(tvStation);
		}
		return tvStation;
	}

	/**
	 * 获取指定电视台节目表
	 * 
	 * @param stationName
	 * @param date
	 * @return
	 */
	@Override
	public List<ProgramTable> getProgramTable(String stationName, String date) {
		return tvDao.getProgramTable(stationName, date);
	}

	/**
	 * 根据电视台分类查询分类下的所有电视台
	 * 
	 * @param classify
	 * @return
	 */
	@Override
	public List<MyTv> getMyTvByClassify(String classify) {
		return tvDao.getMyTvByClassify(classify);
	}

	/**
	 * 判断指定的电视节目表是否已存在
	 * 
	 * @param stationName
	 * @param date
	 * @return
	 */
	@Override
	public boolean isProgramTableExists(String stationName, String date) {
		return tvDao.isProgramTableExists(stationName, date);
	}

	/**
	 * 根据电视台名称、日期抓取电视节目表
	 * 
	 * @param tvStation
	 *            电视台对象
	 * @param date
	 *            日期,yyyy-MM-dd
	 * @param listeners
	 *            抓取事件监听器
	 * @return
	 */
	@Override
	public List<ProgramTable> crawlProgramTable(TvStation tvStation,
			String date, CrawlEventListener... listeners) {
		Crawler crawler = MyTvCrawlerManager.getInstance().newCrawler();
		crawler.registerCrawlEventListener(new CrawlEventListenerAdapter() {

			@Override
			public void crawlEnd(CrawlEvent event) {
				if (event instanceof ProgramTableCrawlEndEvent) {
					List<ProgramTable> resultList = ((ProgramTableCrawlEndEvent) event)
							.getReturnValue();
					ProgramTable[] resultArray = new ProgramTable[resultList
							.size()];
					save(resultList.toArray(resultArray));
				}
			}
		});
		for (int i = 0, length = listeners == null ? 0 : listeners.length; i < length; i++) {
			crawler.registerCrawlEventListener(listeners[i]);
		}
		return crawler.crawlProgramTable(date, tvStation);
	}

	/**
	 * 抓取所有电视台
	 * 
	 * @param listeners
	 *            抓取事件监听器
	 * 
	 * @return
	 */
	@Override
	public List<TvStation> crawlAllTvStation(CrawlEventListener... listeners) {
		Crawler crawler = MyTvCrawlerManager.getInstance().newCrawler();
		final List<TvStation> stationList = new ArrayList<TvStation>();
		crawler.registerCrawlEventListener(new CrawlEventListenerAdapter() {

			@Override
			public void itemFound(CrawlEvent event) {
				if (event instanceof TvStationFoundEvent) {
					TvStation item = ((TvStationFoundEvent) event).getItem();
					synchronized (stationList) {
						int size = stationList.size();
						if (size > 0
								&& size % TV_STATION_PERSISTENT_THRESHOLD == 0) {
							TvStation[] stations = new TvStation[size];
							tvDao.save(stationList.toArray(stations));
							// 保存之后再写入缓存，因为持久化前会判断电视台是否已经存在
							MemoryCache.getInstance().addCache(stationList);
							stationList.clear();
						} else {
							stationList.add(item);
						}
					}
				}
			}

			@Override
			public void crawlEnd(CrawlEvent event) {
				if (event instanceof AllTvStationCrawlEndEvent) {
					synchronized (stationList) {
						if (stationList.size() > 0) {
							TvStation[] stations = new TvStation[stationList
									.size()];
							tvDao.save(stationList.toArray(stations));
							// 保存之后再写入缓存，因为持久化前会判断电视台是否已经存在
							MemoryCache.getInstance().addCache(stationList);
							stationList.clear();
						}
					}
				}
			}
		});
		for (int i = 0, length = listeners == null ? 0 : listeners.length; i < length; i++) {
			crawler.registerCrawlEventListener(listeners[i]);
		}
		return crawler.crawlAllTvStation();
	}

	@Override
	public List<TvStation> getDisplayedTvStation() {
		return tvDao.getDisplayedTvStation();
	}

	@Override
	public List<MyTv> getMyTv() {
		return tvDao.getMyTv();
	}

	@Override
	public boolean isInMyTv(TvStation tvStation) {
		return MemoryCache.getInstance().isInMyTv(tvStation);
	}
}
 public class Constant {
	// 应用名称
	public final static String APP_NAME = "mytv";
	// CPU核数
	public static final int CPU_PROCESSOR_NUM = Runtime.getRuntime()
			.availableProcessors();
	// 数据文件目录
	public final static String MY_TV_DATA_PATH = Config.getDataFilePath()
			+ "data" + File.separator;
	// 数据文件
	public final static String MY_TV_DATA_FILE_PATH = MY_TV_DATA_PATH
			+ "mytv.dat";
	// 电视节目表文件路径
	public final static String CRAWL_FILE_PATH = Constant.MY_TV_DATA_PATH
			+ "crawlfiles" + File.separator;

	public final static String XML_TAG_DB = "db";
	public final static String XML_TAG_DATA = "data";
	public final static String XML_TAG_STATION = "station";
	public final static String XML_TAG_PROGRAM = "program";

	public final static String TV_STATION_INIT_DATA_FILE_NAME = "tv_station.properties";
	public final static String TV_STATION_ALIAS_INIT_DATA_FILE_NAME = "tv_station_alias.properties";

	public final static String DOT = ".";
	public final static String COMMA = ",";
	public final static String UNDERLINE = "_";
	public final static String COLON = ":";

	static {
		File file = new File(MY_TV_DATA_PATH);
		if (!file.exists()) {
			file.mkdirs();
		}

		file = new File(CRAWL_FILE_PATH);
		if (!file.exists()) {
			file.mkdirs();
		}
	}

}
 public class DateUtils {
	private final static Logger logger = LoggerFactory
			.getLogger(DateUtils.class);
	// 默认时间格式
	public static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";

	/**
	 * 获取当天的日期字符串，yyyy-MM-dd
	 * 
	 * @return
	 */
	public static String today() {
		return DateUtils.date2String(Calendar.getInstance().getTime(),
				"yyyy-MM-dd");
	}

	/**
	 * 获取明天的日期字符串，yyyy-MM-dd
	 * 
	 * @return
	 */
	public static String tommorow() {
		Calendar calendar = Calendar.getInstance();
		calendar.add(Calendar.DAY_OF_MONTH, 1);
		return DateUtils.date2String(calendar.getTime(), "yyyy-MM-dd");
	}

	/**
	 * 字符串转为时间
	 * 
	 * @param date
	 * @param pattern
	 * @return
	 */
	public static Date string2Date(String date, String pattern) {
		SimpleDateFormat format = new SimpleDateFormat(pattern);
		try {
			return format.parse(date);
		} catch (ParseException e) {
			logger.error("invalid date value: " + date, e);
		}
		return null;
	}

	/**
	 * 字符串转为时间
	 * 
	 * @param date
	 * @return
	 */
	public static Date string2Date(String date) {
		return string2Date(date, DEFAULT_DATE_PATTERN);
	}

	/**
	 * 时间转为字符串
	 * 
	 * @param date
	 * @return yyyy-MM-dd HH:mm:ss格式
	 */
	public static String date2String(Date date) {
		SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_PATTERN);
		formatter.setTimeZone(TimeZone.getDefault());
		return formatter.format(date);
	}

	/**
	 * 时间转为字符串
	 * 
	 * @param date
	 * @param pattern
	 *            时间样式
	 * @return
	 */
	public static String date2String(Date date, String pattern) {
		SimpleDateFormat formatter = new SimpleDateFormat(pattern);
		formatter.setTimeZone(TimeZone.getDefault());
		return formatter.format(date);
	}

	/**
	 * 获取指定時間一周的日期列表
	 * 
	 * @param date
	 * @return
	 */
	public static Date[] getWeek(Date date) {
		Date[] dates = new Date[7];
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.setFirstDayOfWeek(Calendar.MONDAY);
		calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
		for (int i = 0; i < 7; i++) {
			dates[i] = calendar.getTime();
			calendar.add(Calendar.DAY_OF_YEAR, 1);
		}
		return dates;
	}

	/**
	 * 获取指定时间的下一周头一天, 每周第一天为星期一
	 * 
	 * @param date
	 * @return
	 */
	public static Date nextWeek(Date date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.setFirstDayOfWeek(Calendar.MONDAY);
		calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
		calendar.add(Calendar.WEEK_OF_YEAR, 1);
		return calendar.getTime();
	}

	/**
	 * 获取指定时间一周的日期列表，每周第一天为星期一
	 * 
	 * @param date
	 * @param pattern
	 * @return
	 */
	public static String[] getWeek(Date date, String pattern) {
		Date[] dates = getWeek(date);
		String[] result = new String[dates.length];
		for (int i = 0; i < 7; i++) {
			result[i] = date2String(dates[i], pattern);
		}
		return result;
	}

	/**
	 * 给指定日期加/减年
	 * 
	 * @param date
	 * @param year
	 *            正数加，负数减
	 * @return
	 */
	public static Date addYear(Date date, int year) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.add(Calendar.YEAR, year);
		return calendar.getTime();
	}

	/**
	 * 给指定日期加/减指定月
	 * 
	 * @param date
	 * @param month
	 *            正数加，负数减
	 * @return
	 */
	public static Date addMonth(Date date, int month) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.add(Calendar.MONTH, month);
		return calendar.getTime();
	}

	/**
	 * 给指定日期加/减指定天数
	 * 
	 * @param date
	 * @param day
	 *            正数加，负数减
	 * @return
	 */
	public static Date addDay(Date date, int day) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.add(Calendar.DAY_OF_MONTH, day);
		return calendar.getTime();
	}

	/**
	 * 计算两个指定日期相差几天
	 * 
	 * @param arg0
	 * @param arg1
	 * @return 返回大减小的相差天数
	 */
	public static long differHowManyDays(Date arg0, Date arg1) {
		long d1 = arg0.getTime();
		long d2 = arg1.getTime();

		return Math.abs(d1 - d2) / (60 * 60 * 24 * 1000);
	}

	/**
	 * 取得一天的开始时间
	 * 
	 * @param date
	 * @return
	 */
	public static Date getStartOfTheDay(Date date) {
		String value = date2String(date, "yyyy-MM-dd 00:00:00");
		return string2Date(value);
	}

	/**
	 * 取得一天的结束时间
	 * 
	 * @param date
	 * @return
	 */
	public static Date getEndOfTheDay(Date date) {
		String value = date2String(date, "yyyy-MM-dd 23:59:59");
		return string2Date(value);
	}

	/**
	 * 取得指定月份的第一天
	 * 
	 * @param date
	 * @return
	 */
	public static Date getFirstDayOfTheMonth(Date date) {
		String value = date2String(date, "yyyy-MM-01 00:00:00");
		return string2Date(value);
	}

	/**
	 * 取得指定月份的最后一天
	 * 
	 * @param date
	 * @return
	 */
	public static Date getLastDayOfTheMonth(Date date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		int lastDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
		calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
				lastDay, 0, 0, 0);
		return calendar.getTime();
	}

}
 public class MemoryCache {
	private final static ConcurrentHashSet<TvStation> ALL_TV_STATION = new ConcurrentHashSet<TvStation>();
	private final static ConcurrentHashSet<MyTv> ALL_MY_TV = new ConcurrentHashSet<MyTv>();

	private MemoryCache() {
	}

	public static MemoryCache getInstance() {
		return MemoryCacheSingleton.MEMORY_CACHE;
	}

	private final static class MemoryCacheSingleton {
		private final static MemoryCache MEMORY_CACHE = new MemoryCache();
	}

	/**
	 * 缓存所有电视台
	 * 
	 * @param stations
	 */
	public void addCache(List<TvStation> stations) {
		ALL_TV_STATION.addAll(stations);
	}

	/**
	 * 缓存电视台
	 * 
	 * @param stations
	 */
	public void addCache(TvStation... stations) {
		for (TvStation station : stations) {
			ALL_TV_STATION.add(station);
		}
	}

	/**
	 * 根据名称查找电视台对象
	 * 
	 * @param stationName
	 * @return
	 */
	public TvStation getStation(String stationName) {
		for (TvStation station : ALL_TV_STATION) {
			if (station.getName().equals(stationName)) {
				return station;
			}
		}
		return null;
	}

	/**
	 * 将客户端显示的电视台加入缓存
	 * 
	 * @param stations
	 */
	public void addMyTvCache(List<MyTv> mytvList) {
		ALL_MY_TV.addAll(mytvList);
	}

	/**
	 * 判断电视台是否是客户端显示的电视台
	 * 
	 * @param tvStation
	 * @return
	 */
	public boolean isInMyTv(TvStation tvStation) {
		for (MyTv myTv : ALL_MY_TV) {
			if (myTv.getStationName().equals(tvStation.getName())) {
				return true;
			}
		}
		return false;
	}
}

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.laudandjolynn.mytv.datasource.DataSourceManager;
import com.laudandjolynn.mytv.event.CrawlEvent;
import com.laudandjolynn.mytv.event.CrawlEventListener;
import com.laudandjolynn.mytv.event.CrawlEventListenerAdapter;
import com.laudandjolynn.mytv.event.TvStationFoundEvent;
import com.laudandjolynn.mytv.exception.MyTvException;
import com.laudandjolynn.mytv.model.MyTv;
import com.laudandjolynn.mytv.model.TvStation;
import com.laudandjolynn.mytv.proxy.ConfigProxy;
import com.laudandjolynn.mytv.proxy.MyTvProxyManager;
import com.laudandjolynn.mytv.service.TvService;
import com.laudandjolynn.mytv.service.TvServiceImpl;
import com.laudandjolynn.mytv.utils.Constant;
import com.laudandjolynn.mytv.utils.DateUtils;
import com.laudandjolynn.mytv.utils.MemoryCache;

/*******************************************************************************
 * Copyright 2015 htd0324@gmail.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.laudandjolynn.mytv;




/**
 * @author: Laud
 * @email: htd0324@gmail.com
 * @date: 2015年3月25日 上午9:12:56
 * @copyright: www.laudandjolynn.com
 */
public class Main {
	private final static Logger logger = LoggerFactory.getLogger(Main.class);

	public static void main(String[] args) {
		// 初始化应用状态、启动内部任务
		startService();
		com.laudandjolynn.mytv.Server hessian = new HessianServer();
		try {
			hessian.start();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new MyTvException(e);
		}
		com.laudandjolynn.mytv.Server rmi = new RmiServer();
		try {
			rmi.start();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new MyTvException(e);
		}
		logger.info("My TV Program Table Crawler is running.");
	}

	/**
	 * 启动应用
	 */
	private static void startService() {
		logger.info("start My TV Program Table Crawler.");
		// 加载应用数据
		MyTvData data = new MyTvData();
		// 创建数据库及表结构
		initDb(data);
		// 初始化数据库数据
		initDbData0(data);
		initDbData(data);
	}

	@SuppressWarnings("unchecked")
	private static List<String> loadSql() {

		Object object = DataSourceManager.DATA_SOURCE_PROP
