OfflineService.java

package sk.iway.iwcm.components.offline;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.HttpClientBuilder;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import sk.iway.iwcm.Constants;
import sk.iway.iwcm.DB;
import sk.iway.iwcm.DBPool;
import sk.iway.iwcm.Identity;
import sk.iway.iwcm.LabelValueDetails;
import sk.iway.iwcm.Logger;
import sk.iway.iwcm.PathFilter;
import sk.iway.iwcm.SetCharacterEncodingFilter;
import sk.iway.iwcm.Tools;
import sk.iway.iwcm.common.DocTools;
import sk.iway.iwcm.doc.DocDB;
import sk.iway.iwcm.doc.DocDetails;
import sk.iway.iwcm.doc.GroupDetails;
import sk.iway.iwcm.doc.GroupsDB;
import sk.iway.iwcm.doc.RelatedPagesDB;
import sk.iway.iwcm.doc.TemplateDetails;
import sk.iway.iwcm.doc.TemplatesDB;
import sk.iway.iwcm.i18n.Prop;
import sk.iway.iwcm.system.ConfDB;
import sk.iway.iwcm.system.cluster.ClusterDB;
import sk.iway.iwcm.system.zip.ZipEntry;
import sk.iway.iwcm.system.zip.ZipOutputStream;

public class OfflineService {
    Map<String, String> downloadedUrls = new Hashtable<>();
	List<LabelValueDetails> needDownloadUrlsList = new ArrayList<>();
	Map<String, String> allreadyHasInDownloadList = new Hashtable<>();
	private static final int BUFFER = 8192;

	public static final String CROP_START = "<!-- offline crop start -->";
	public static final String CROP_END = "<!-- offline crop end -->";

	public void execute(
         Identity user,
         HttpServletRequest request,
         HttpServletResponse response) throws IOException
    {
		Logger.println(OfflineAction.class,"offlineAction");

		//setni usera do servlet contextu, ten sa potom vybera v PathFilter (inak nie je mozne preniest login)
		Constants.getServletContext().setAttribute(Constants.USER_KEY, user);


		Prop prop = Prop.getInstance(request);

		response.setContentType("text/html; charset="+SetCharacterEncodingFilter.selectEncoding(request));
		PrintWriter out = response.getWriter();
		out.println("<html><head><LINK rel='stylesheet' href='/admin/css/style.css'></head><body>");
		out.println("<h3>"+prop.getText("admin.offline.generating_html_please_wait")+"</h3>");
		out.flush();
		for (int i=0; i<100; i++)
		{
			out.println("<!-- generating --------------------------->");
			out.flush();
		}

		//ziskaj si zoznam DocId
		List<DocDetails> documents = new ArrayList<>();
		StringBuilder searchGroups = null;
		GroupsDB groupsDB = GroupsDB.getInstance();
		int groupId = -1;
		if(request.getParameter("groupId")!=null) groupId = Tools.getIntValue(request.getParameter("groupId"), groupId);

//		najdi child grupy
		for (GroupDetails group : groupsDB.getGroupsTree(groupId, true, true))
		{
			if (group != null && group.isInternal()==false)
			{
				if (searchGroups == null)
				{
					searchGroups = new StringBuilder(Integer.toString(group.getGroupId()));
				}
				else
				{
					searchGroups.append("," + group.getGroupId());
				}
			}
		}
		Connection db_conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		String query = "";
		if (groupId == -1 || searchGroups == null)
			query = "SELECT doc_id, title, virtual_path FROM documents WHERE available=? ORDER BY doc_id";
		else
			query = "SELECT doc_id, title, virtual_path FROM documents WHERE available=? AND group_id IN ("+ searchGroups.toString() +") ORDER BY doc_id";
		try
		{
			db_conn = DBPool.getConnection();
			ps = db_conn.prepareStatement(query);
			ps.setBoolean(1, true);
			rs = ps.executeQuery();
			while (rs.next())
			{
				DocDetails doc = new DocDetails();
				doc.setDocId(rs.getInt("doc_id"));
				doc.setTitle(DB.getDbString(rs, "title"));
				doc.setVirtualPath(DB.getDbString(rs, "virtual_path"));
				documents.add(doc);
			}
			rs.close();
			ps.close();
			rs = null;
			ps = null;
		}
		catch (Exception ex)
		{
			sk.iway.iwcm.Logger.error(ex);
		}
		finally
		{
			try
			{
				if (db_conn != null)
					db_conn.close();
				if (rs != null)
					rs.close();
				if (ps != null)
					ps.close();
			}
			catch (Exception ex2)
			{
				sk.iway.iwcm.Logger.error(ex2);
			}
		}

		//debug
		/*documents = new ArrayList();
		doc = new DocDetails();
		doc.setDocId(4);
		doc.setTitle("main");
		documents.add(doc);*/

		String destination = "/html";

		if(request.getParameter("destination")!=null) destination = request.getParameter("destination");

		if(Tools.isEmpty(destination)){
			destination = "/html";
		}
		else
		{
			destination = destination.trim();
			if(destination.charAt(0) != '/')
				destination = "/"+destination;
		}

		HttpURLConnection.setFollowRedirects(false);

		String data;
		File f;
		OutputStreamWriter osw;

		f = new File(Tools.getRealPath(destination));

		if (Tools.isNotEmpty(PathFilter.getCustomPath()))
		{
			f = new File(PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar + destination.substring(1));
		}

		if (f.exists())
		{
			//vymaz obsah
			File[] files = f.listFiles();
			int i;
			for (i=0; i<files.length; i++)
			{
				if(files[i].delete() == false)
				   Logger.error(OfflineAction.class, "Unable to delete file: "+files[i].getName());
			}
		}
		else
		{
			if(f.mkdirs() == false)
			{
				out.println("Unable to cretate directory: "+PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar + "html");
				out.println("<br />Archive creation stopped");
				return;
			}
		}

		String basePath = Tools.getBaseHrefLoopback(request);
		int flushCounter = 0;
		String fileName;

		downloadedUrls = new Hashtable<>();
		needDownloadUrlsList = new ArrayList<>();
		allreadyHasInDownloadList = new Hashtable<>();

		TemplatesDB templatesDB = null;
		DocDB docDB = null;
		TemplateDetails td = null;
		String pageFcieFileName = null;
		File pageFcieFile = null;
		String pageFcieData = null;

		//#11325: vypinam spam protection
   		if(ConfDB.setName("disableSpamProtectionForOffline", "true") && ClusterDB.isServerRunningInClusterMode()) {
   			ClusterDB.addRefresh("sk.iway.iwcm.system.ConfDB-disableSpamProtectionForOffline");
		}

		int counter = 0;
		int size = documents.size();
		for (DocDetails doc : documents)
		{
			counter++;

			//if (doc.getDocId() > 100) continue;

			out.println("["+counter+"/"+size+"] id="+ doc.getDocId() + " title=" + doc.getTitle());
			Logger.println(OfflineAction.class,"["+counter+"/"+size+"] id="+ doc.getDocId() + " title=" + doc.getTitle());

			try
			{
				if (Tools.isEmpty(doc.getVirtualPath()))
				{
					data = downloadUrl(basePath + "/showdoc.do?docid="+doc.getDocId(), request);
				}
				else
				{
					data = downloadUrl(basePath + doc.getVirtualPath(), request);
				}

				if (Tools.isNotEmpty(data))
				{
					//uprav cesty
					data = fixPath(data, basePath);

					/*
					 * /components/_common/javascript/page_functions.js.jsp?language=LNG stiahnem ako page_functions_LNG.js do destination a fixnem linky
					 * ticket #11321
					 */
					if(data.indexOf("/components/_common/javascript/page_functions.js.jsp?language=") != -1)
					{
						if(templatesDB == null) templatesDB = TemplatesDB.getInstance();
						if(docDB == null) docDB = DocDB.getInstance();
						td = templatesDB.getTemplate(docDB.getBasicDocDetails(doc.getDocId(), true).getTempId());
						if(td != null && td.getTempId() > 0)
						{
							pageFcieFileName = "page_functions_"+td.getLng()+".js";
							pageFcieFile = new File(Tools.getRealPath(destination + "/" + pageFcieFileName)); //NOSONAR
							if (Tools.isNotEmpty(PathFilter.getCustomPath()))
								pageFcieFile = new File(PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar + destination.substring(1) + File.separatorChar + pageFcieFile.getName());

							if(pageFcieFile.exists() == false)
							{
								pageFcieData = downloadUrl(basePath + "/components/_common/javascript/page_functions.js.jsp?language="+td.getLng(), request);
								osw = new OutputStreamWriter(new FileOutputStream(pageFcieFile), SetCharacterEncodingFilter.getEncoding());
								osw.write(pageFcieData);
								osw.close();
							}

							data = Tools.replace(data, "../components/_common/javascript/page_functions.js.jsp?language="+td.getLng(), pageFcieFileName);
							data = Tools.replace(data, "/components/_common/javascript/page_functions.js.jsp?language="+td.getLng(), pageFcieFileName);
						}
					}

					//uloz stranku
					fileName = getFileName("showdoc.do?docid="+doc.getDocId());
					f = new File(Tools.getRealPath(destination + "/" + fileName)); //NOSONAR
					if (Tools.isNotEmpty(PathFilter.getCustomPath()))
					{
						f = new File(PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar + destination.substring(1) + File.separatorChar + f.getName());
					}

					osw = new OutputStreamWriter(new FileOutputStream(f), SetCharacterEncodingFilter.getEncoding());
					osw.write(data);
					osw.close();

					out.println(" [OK]");

					flushCounter++;
					if (flushCounter%10==0)
					{
						out.println("<script language='javascript'>window.scrollBy(0,1000);</script>");
						out.println("<!-- generating --------------------------->");
						out.println("<!-- generating --------------------------->");
						out.println("<!-- generating --------------------------->");
						out.println("<!-- generating --------------------------->");
						out.flush();
					}

					downloadedUrls.put("/showdoc.do?docid="+doc.getDocId(), destination + "/" + fileName);
					if (Tools.isNotEmpty(doc.getVirtualPath()))
					{
						downloadedUrls.put(doc.getVirtualPath(), destination + "/" + fileName);
					}
				}
			}
			catch (Exception ex2)
			{
				out.println("CHYBA: "+ex2.getMessage());
			}

			out.println("<br>");
		}

		//if (1==1) return(null);

		//nacitaj specialne veci, ktore tam zostali (komponenty)
		String link;
        String newLink;
		LabelValueDetails lvd;
		int i=0;
		while (i < needDownloadUrlsList.size())
		{
			lvd = needDownloadUrlsList.get(i++);
			try
			{
				link = lvd.getLabel();
				if (link.indexOf("rnd=")!=-1)
				{
					//linka ma nahodny parameter, preskoc, lebo sa nam to zacykli
					continue;
				}
				if (link.indexOf("__fp=")!=-1)
				{
					//linka na nejaky stripes form, preskoc
					continue;
				}
				if (link.indexOf("?d-")!=-1 && (link.indexOf("-e=")!=-1 || link.indexOf("-p=")!=-1))
				{
					//linka na nejaky displaytag form, preskoc
					continue;
				}
				if (link.indexOf("forceBrowserDetector=")!=-1)
				{
					//linka na inu vizualnu podobu, preskoc
					continue;
				}
				if (downloadedUrls.get(link)==null && link.startsWith("/admin")==false)
				{
					//este to nie je stiahnute, treba stiahnut
					newLink = lvd.getValue();
					out.println("<br/>NEED ["+i+"/"+needDownloadUrlsList.size()+"]: " + link + " -> " + newLink);
					if (link.startsWith("../")) link = link.substring(2);
					if (!link.startsWith("/")) link = "/" + link; //NOSONAR

					if (link.startsWith("/#") || link.startsWith("/files") || link.startsWith("/images") || link.startsWith("/jscripts") ||
						 link.endsWith(".doc") || link.endsWith(".xls") || link.endsWith(".ppt") || link.endsWith(".pdf"))
					{
						downloadedUrls.put(link, link);
						continue;
					}

					//if (1==1) continue;

					//	otestuj, ci to nie je existujuci subor
					f = new File(Tools.getRealPath(link));
					if (Tools.isNotEmpty(PathFilter.getCustomPath()))
					{
						f = new File(PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar + link.replace('/', File.separatorChar));
					}
					out.println("checking: " + f.getAbsolutePath());
					if (f.exists())
					{
						downloadedUrls.put(link, newLink);
						out.println(" [EXISTING FILE OK]<br>");
						continue;
					}


					data = downloadUrl(basePath + link, request);
					if (Tools.isNotEmpty(data))
					{
						//	uprav cesty
						data = fixPath(data, basePath);

						//odstran nepotrebny HTML kod
						data = cropData(data);

						f = new File(Tools.getRealPath( destination +"/" + newLink)); //NOSONAR
						if (Tools.isNotEmpty(PathFilter.getCustomPath()))
						{
							f = new File(PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar + destination.substring(1) + File.separatorChar + f.getName());
						}

						osw = new OutputStreamWriter(new FileOutputStream(f), SetCharacterEncodingFilter.getEncoding());
						osw.write(data);
						osw.close();

						out.println(" [OK]");

						downloadedUrls.put(link, newLink);
					}
					else
					{
						out.println("--> data is empty: " + link);
					}
					out.println("<br>");
				}
				else
				{
					//out.println("UZ MAM: " + link + "<br>");
				}
			}
			catch (Exception ex)
			{
				out.println("<span class='error'>"+ex.getMessage()+"</span>");
				sk.iway.iwcm.Logger.error(ex);
			}
		}

		//#11325: zapinam spam protection
		ConfDB.deleteName("disableSpamProtectionForOffline");
		Constants.deleteConstant("disableSpamProtectionForOffline");

		Constants.getServletContext().removeAttribute(Constants.USER_KEY);

		//ak treba, vygeneruj not_available_on_cd.html
		f = new File(Tools.getRealPath(destination +"/not_available_on_cd.html"));
		if (Tools.isNotEmpty(PathFilter.getCustomPath()))
		{
			f = new File(PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar + destination.substring(1)+ "/not_available_on_cd.html");
		}

		if (f.exists()==false)
		{
			data = "<html><body>"+prop.getText("components.offline.notAvailableOnCD")+"</body></html>";
			osw = new OutputStreamWriter(new FileOutputStream(f), SetCharacterEncodingFilter.getEncoding());
			osw.write(data);
			osw.close();
		}

		//	ak treba, vygeneruj blank.html
		f = new File(Tools.getRealPath( destination+"/blank.html"));
		if (Tools.isNotEmpty(PathFilter.getCustomPath()))
		{
			f = new File(PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar + destination.substring(1) + "/blank.html");
		}

		if (f.exists()==false)
		{
			data = "<html><body></body></html>";
			osw = new OutputStreamWriter(new FileOutputStream(f), SetCharacterEncodingFilter.getEncoding());
			osw.write(data);
			osw.close();
		}

		out.println("<hr>" +prop.getText("admin.offline.generate_done")+ "<br><br>");

		String archiveDirs = request.getParameter("archiveDirs");
		String makeZipArchive = request.getParameter("makeZipArchive");
		if (Tools.isNotEmpty(archiveDirs) && Tools.isNotEmpty(makeZipArchive) && "yes".equals(makeZipArchive))
		{
			makeZipArchive(Constants.getServletContext(), out, archiveDirs, basePath);
		}

		out.println("</body></html>");

		HttpURLConnection.setFollowRedirects(true);
   }

	/**
	 * Vrati nazov suboru, pod ktorym bude stranka na disku
	 * @param url
	 * @return
	 */
	private String getFileName(String url)
	{
		String testUrl = url;
		if (testUrl.startsWith("../"))
		{
			testUrl = testUrl.substring(2);
		}
		if (testUrl.startsWith("/")==false)
		{
			testUrl = "/"+testUrl;
		}

		if (testUrl.startsWith("/files/") || testUrl.startsWith("/images/"))
		{
			//je to linka na subor, alebo obrazok
			return(".."+testUrl);
		}

		//otestuj, ci to nie je existujuci subor
		File f = new File(Tools.getRealPath(testUrl));
		if (Tools.isNotEmpty(PathFilter.getCustomPath()))
		{
			f = new File(PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar + testUrl.replace('/', File.separatorChar));
		}

		Logger.println(OfflineAction.class,"test: " + testUrl + " file="+f.getAbsolutePath() + " ex="+f.exists());

		if (f.exists())
		{
			return(".."+testUrl);
		}

		int index = url.lastIndexOf('/');
		if (index != -1)
		{
			url = url.substring(index+1);
		}

		try
		{
			url = URLDecoder.decode(url, SetCharacterEncodingFilter.getEncoding());
		}
		catch (UnsupportedEncodingException ex)
		{
			sk.iway.iwcm.Logger.error(ex);
		}

		//odstran vsetky zle znaky
		url = DocTools.removeChars(url);

		url = Tools.replace(url, "showdoc.do_docid=", "showdoc_");

		url = DB.internationalToEnglish(url.toLowerCase());

		if (url.indexOf("rnd=")!=-1)
		{
			//stranka s nahodnym parametrom
			url = url.substring(0, url.indexOf("rnd="));
		}

		url = url + ".html";

		return(url);
	}

	/**
	 * Opravi cesty k obrazkom, suborom, inym strankam...
	 * @param data
	 * @return
	 */
	private String fixPath(String data, String baseHref)
	{
		//odstran google analytics
		data = Tools.replace(data, "src=\"http://www.google-analytics.com/urchin.js", "");
		data = Tools.replace(data, "src='http://www.google-analytics.com/urchin.js", "");
		data = Tools.replace(data, "urchinTracker();", "");

		//obrazky
		data = Tools.replace(data, "src='/", "src='../");
		data = Tools.replace(data, "src=\"/", "src=\"../");
		data = Tools.replace(data, "src=\"images", "src=\"../images");
		data = Tools.replace(data, "src='images", "src='../images");
		data = Tools.replace(data, "url(images/", "url(../images/");
		data = Tools.replace(data, "url(/images/", "url(../images/");
		//ak je tam nejaka JS funkcia, tam to treba spravit tiez
		data = Tools.replace(data, "'images/", "'../images/");
		//linka vo Flashi
		data = Tools.replace(data, "VALUE=\"/images/", "VALUE=\"../images/");
		data = Tools.replace(data, "value=\"/images/", "value=\"../images/");
		data = Tools.replace(data, "value='/images/", "value='../images/");
		data = Tools.replace(data, "VALUE=\"images/", "VALUE=\"../images/");
		data = Tools.replace(data, "value=\"flash/", "value=\"../flash/");
		data = Tools.replace(data, "value=\"/flash/", "value=\"../flash/");
		data = Tools.replace(data, "data=\"/images/", "data=\"../images/");
		data = Tools.replace(data, "data='/images/", "data='../images/");

		//flash
		data = Tools.replace(data, "<PARAM NAME=\"Movie\" VALUE=\"/", "<PARAM NAME=\"Movie\" VALUE=\"../");
		data = Tools.replace(data, "<PARAM NAME=\"Src\" VALUE=\"/", "<PARAM NAME=\"Src\" VALUE=\"../");
		data = Tools.replace(data, "/components/_common/preload.swf?path=/", "../");

		//subory
		data = Tools.replace(data, "href=\"/files", "href=\"../files");
		data = Tools.replace(data, "href='/files", "href='../files");

		//javascript
		data = Tools.replace(data, "src=\"jscripts/", "src=\"../jscripts/");
		data = Tools.replace(data, "src='jscripts/", "src='../jscripts/");

		//css
		data = Tools.replace(data, "href='/css", "href='../css");
		data = Tools.replace(data, "href=\"/css", "href=\"../css");
		data = Tools.replace(data, "href=\"css", "href=\"../css");
		data = Tools.replace(data, "href='css", "href='../css");
		data = Tools.replace(data, "@import /css", "@import ../css");
		data = Tools.replace(data, "@import '/css", "@import '../css");
		data = Tools.replace(data, "@import \"/css", "@import \"../css");

		//odkazy
		//Logger.println(OfflineAction.class,"--> ");

		ByteArrayInputStream is = new ByteArrayInputStream(data.getBytes());

		// set tidy parameters
		try {
			Document doc = Jsoup.parse(is, SetCharacterEncodingFilter.getEncoding(), baseHref);
			data = extractLinks(doc, data);
		} catch (IOException e) {
			Logger.error(e);
		}

	   //uprav submity formularov
	   data = Tools.replace(data, "action='showdoc.do'", "action='not_available_on_cd.html'");
	   data = Tools.replace(data, "action='/showdoc.do'", "action='not_available_on_cd.html'");
	   data = Tools.replace(data, "action=\"showdoc.do\"", "action=\"not_available_on_cd.html\"");
	   data = Tools.replace(data, "action=\"/showdoc.do\"", "action=\"not_available_on_cd.html\"");

	   //https redirecty v JS
	   data = Tools.replace(data, "if (window.location.href.indexOf(\"http", "if (false && window.location.href.indexOf(\"http");
	   data = Tools.replace(data, "if (window.location.href.indexOf('http", "if (false && window.location.href.indexOf('http");

		return(data);
	}

	/**
	 * Odstrani z HTML kodu vsetko medzi CROP_START a CROP_END
	 * @param data
	 * @return
	 */
	private String cropData(String data)
	{
		int failsafe = 0;
		int start = data.indexOf(CROP_START);
		int end = data.indexOf(CROP_END);

		while (start>0 && end > start && failsafe++<50)
		{
			try
			{
				data = data.substring(0, start) + data.substring(end+CROP_END.length());

				start = data.indexOf(CROP_START, start+1);
				end = data.indexOf(CROP_END, end+1);
			}
			catch (Exception e)
			{
				sk.iway.iwcm.Logger.error(e);
			}
		}

		return data;
	}

	@SuppressWarnings("deprecation")
	private String extractLinks(Document doc, String data)
	{
		try
		{
			Elements links = doc.select("a[href]"); // a with href
			for (Element e : links) {
				String link = e.attributes().get("href");

				if (link!=null && Tools.isNotEmpty(link) && link.toLowerCase().startsWith("http")==false && link.toLowerCase().startsWith("mailto")==false)
				{
					Logger.debug(OfflineAction.class, "Extracting link: " + link);

					//	odstran zahradku
					if (link.indexOf('#')>1)
					{
						link = link.substring(0, link.indexOf('#'));
					}

					boolean isLinkDirect = false;
					//otestuj, ci to nie je linka na nejaku skratenu cestu
					DocDB docDB = DocDB.getInstance();

					int docId = docDB.getVirtualPathDocId(link);
					if (docId > 0)
					{
						String newLink = getFileName("showdoc.do?docid="+docId);
						Logger.debug(OfflineAction.class, "Replacing link: link="+link+" newLink="+newLink);
						data = replaceLink(data, link, newLink);
						if (downloadedUrls.containsKey(link)==false)
						{
							//needDownloadUrlsList.add(new LabelValueDetails(link, newLink));

							//je to linka na normalne docId, to nemusim stahovat, stiahne sa automaticky (ako vsetky ostatne docid)
							downloadedUrls.put(link, newLink);
							Logger.println(OfflineAction.class,"link DIRECT: " + link + " new:"+newLink);
						}
						isLinkDirect = true;
					}

					if (isLinkDirect == false)
					{
						//ziskaj docid
						int start = link.indexOf("docid=");
						if (start > 0)
						{
							String tmp = link.substring(start);
							StringTokenizer st = new StringTokenizer(tmp, "=&'\", #");
							if (st.countTokens()<2)
							{
								Logger.println(OfflineAction.class,"zla linka: " + tmp);
							}
							else
							{
								//preskocime docid=
								st.nextToken();
								docId = Tools.getIntValue(st.nextToken(), -1);
								if (docId > 0)
								{
									if (link.startsWith("javascript:"))
									{
										Logger.println(OfflineAction.class,"som JS");
										if (link.indexOf("wjPopup")!=-1)
										{
											Logger.println(OfflineAction.class,"som wjPopup");
											//ok, je to popup, treba to preparsovat a ziskat len linku
											start = link.indexOf("/showdoc.do");
											int end = link.indexOf("'", start+2);

											Logger.println(OfflineAction.class,"start="+start+" end="+end);

											if (start > 0 && end > start)
											{
												link = link.substring(start, end);
												String newLink = getFileName(link);
												Logger.println(OfflineAction.class,"link="+link+" newLink="+newLink);
												data = replaceLink(data, link, newLink);
											}
										}
									}
									else
									{
										String newLink = getFileName(link);
										data = replaceLink(data, link, newLink);
										if (downloadedUrls.get(link)==null)
										{
											addToDownloadList(link, newLink);
											//needDownloadUrlsList.add(new LabelValueDetails(link, newLink));
											Logger.println(OfflineAction.class,"link: " + link + " new:"+newLink);
											if (link.indexOf("2670400")!=-1)
											{
												Logger.println(OfflineAction.class,"===== MAM ====");
												Logger.println(OfflineAction.class,link + "->" + newLink);
												Logger.println(OfflineAction.class,"==============");
											}
										}
									}
								}
							}

						}
						else if (link.toLowerCase().startsWith("javascript")==false && "/".equals(link)==false && link.startsWith("www.")==false)
						{
							//ak to nie je ani javascript ani / (lebo sa potom nahradza </html> ani www.nieco

							//pridaj do zoznamu na download
							String newLink = getFileName(link);
							data = replaceLink(data, link, newLink);
							if (downloadedUrls.get(link)==null)
							{
								addToDownloadList(link, newLink);
								//needDownloadUrlsList.add(new LabelValueDetails(link, newLink));
								Logger.println(OfflineAction.class,"link DIRECT: " + link + " new:"+newLink);
							}
						}
					}
				}
			}
		}
		catch (Exception ex)
		{
			sk.iway.iwcm.Logger.error(ex);
		}
		return(data);
	}

	/**
	 * Ak uz stranka nie je v zozname na download, prida ju do zoznamu
	 * @param link - linka
	 * @param newLink - nove URL stranky
	 */
	private void addToDownloadList(String link, String newLink)
	{
		if (allreadyHasInDownloadList.get(link)==null && link.startsWith("/admin/")==false)
		{
			needDownloadUrlsList.add(new LabelValueDetails(link, newLink));
			allreadyHasInDownloadList.put(link, newLink);
		}
	}

	private String replaceLink(String src, String oldStr, String newStr)
	{
		//pridaj na download
		if (downloadedUrls.get(oldStr)==null)
		{
			addToDownloadList(oldStr, newStr);
			//needDownloadUrlsList.add(new LabelValueDetails(oldStr, newStr));
		}

		if (src == null)
		{
			return (null);
		}
		if (src.indexOf(oldStr) == -1)
		{
			return (src);
		}
		StringBuilder result = new StringBuilder(src.length() + 50);
		int startIndex = 0;
		int endIndex = src.indexOf(oldStr);
		while (endIndex != -1)
		{
			if ((src.charAt(endIndex+oldStr.length())=='\'' ||
				 src.charAt(endIndex+oldStr.length())=='"' ||
				 src.charAt(endIndex+oldStr.length())==' ' ||
				 src.charAt(endIndex+oldStr.length())=='#') &&
				 (src.charAt(endIndex-1)=='\'' ||
				 src.charAt(endIndex-1)=='"' ||
				 src.charAt(endIndex-1)==' ' ||
				 src.charAt(endIndex-1)=='#'))
			{
				//replacujeme len kompletne linky, preto kontrolujeme koniec na '"_
				result.append(src.substring(startIndex, endIndex));
				result.append(newStr);
				startIndex = endIndex + oldStr.length();
				endIndex = src.indexOf(oldStr, startIndex);
			}
			else
			{
				//preskakujeme
				endIndex = src.indexOf(oldStr, endIndex+1);
			}
		}
		result.append(src.substring(startIndex, src.length()));
		return result.toString();
	}



	/**
	 * Vytvori ZIP-archiv Webroot adresara, ak su
	 * @param servletContext
	 * @param mainDirs - korenove adresare, ktore sa maju zalohovat
	 * @param printWriter
	 */
	private void makeZipArchive(ServletContext servletContext, PrintWriter printWriter, String mainDirs, String basePath)
	{
		if ((","+mainDirs+",").indexOf(",html,")==-1)
		{
			//html adresar sa musi archivovat, naviac na zaciatku neexistuje, takze ho tam
			//pouzivatel ani nevie zadat
			mainDirs = mainDirs + ",html";
		}

		printWriter.println("<b>Archiving...</b><br><br>");
		Logger.println(OfflineAction.class,"Archiving..."+mainDirs);
		double time;
		String outFilename;
		String zipFilePath;
		String[] archiveDirs = {"css", "files", "images", "jscripts","html"};
		File f;
		String zipDirPath;

	   try
		{

			f = new File(Tools.getRealPath("/"));
			zipDirPath = Tools.getRealPath("/");

			/*if (Tools.isNotEmpty(PathFilter.getCustomPath()))
			{
				f = new File(PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar);
				zipDirPath = PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar;
			}*/

			//Logger.println(OfflineAction.class,"zipDirPath: "+zipDirPath);

	   	SimpleDateFormat dateFormat = new SimpleDateFormat(Constants.getString("dateFormat"));
	   	SimpleDateFormat timeFormat = new SimpleDateFormat(Constants.getString("timeFormat"));
	   	long dateTime = Tools.getNow();
	   	//poskladam nazov suboru
	   	String fileName = "offline-" +Constants.getInstallName() +"-"+ dateFormat.format(new Timestamp(dateTime)) +"-"+ timeFormat.format(new Timestamp(dateTime)) +".zip";

	   	//z nazvu suboru vyhodim zakazane znaky
	   	fileName = DocTools.removeChars(fileName);
	   //	if (!zipDirPath.startsWith("/"))  zipDirPath = "/" + zipDirPath;
	   //	if (!zipDirPath.endsWith("/"))  zipDirPath = zipDirPath + "/";

	   	//poskladam cestu archivu
	   	zipFilePath = zipDirPath + fileName;

	   	//Logger.println(OfflineAction.class,"zipFilePath: "+zipFilePath);

	   	//vytvorime si ZIP file
	      //outFilename = Tools.getRealPath(zipFilePath);
	   	outFilename = zipFilePath;
	      ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(outFilename));

	       //zmeriame cas operacie
	       time = Tools.getNow();

	      if (Tools.isNotEmpty(mainDirs))
	   	{
	   		//rozparsujeme si adresare, ktore sa budu archivovat
		   	archiveDirs = RelatedPagesDB.getTokens(mainDirs, ",");
	   	}

	       //iterujeme po adresaroch a pridavame subory do ZIP-archivu
	       for (int i=0; i<archiveDirs.length; i++)
	       {
	       	getFilesFromDir(Tools.getRealPath("/"+archiveDirs[i]), basePath, servletContext, zipOut, printWriter);
	       }

	      //pridavam index.html pre offline verziu
         String data;

         	fileName = "index-offline.htm";
				data = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Frameset//EN\">"+
						"<HTML><HEAD><TITLE>WebJet</TITLE>"+
						"<META http-equiv=Content-Type content=\"text/html; charset=windows-1250\"></HEAD>"+
						"<FRAMESET frameSpacing=0 frameBorder=0 cols=0,*><FRAME src=\"html/blank.htm\" noResize>"+
						"<FRAME name=RightMain src=\"html/" +getFileName("showdoc.do?docid=4")+ "\" scrolling=yes>"+
						"<NOFRAMES><a href='html/" +getFileName("showdoc.do?docid=4")+ "'>WebJet</a>"+
						"</NOFRAMES></FRAMESET></HTML>";

				//uloz stranku
				f = new File(Tools.getRealPath("/" + fileName)); //NOSONAR
				if (Tools.isNotEmpty(PathFilter.getCustomPath()))
				{
					f = new File(PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar + f.getName());
				}

				 //Add ZIP entry to output stream.
				 zipOut.putNextEntry(new ZipEntry(f.getAbsolutePath()));
		     	 zipOut.write(data.getBytes());

	          // Complete the entry
	          zipOut.closeEntry();


			//pridavam blank.html pre offline verziu
			data = "<html><body></body></html>";
				fileName = "blank.htm";
				f = new File(Tools.getRealPath("/html/" + fileName));
				if (Tools.isNotEmpty(PathFilter.getCustomPath()))
				{
					f = new File(PathFilter.getCustomPath() + File.separatorChar + Constants.getInstallName() + File.separatorChar+ "html" + File.separatorChar + f.getName());
				}
				 //Add ZIP entry to output stream.
				 zipOut.putNextEntry(new ZipEntry(f.getAbsolutePath()));
		     	 zipOut.write(data.getBytes());

	          // Complete the entry
	          zipOut.closeEntry();

	       // uzavretie ZIP suboru
	       zipOut.close();
	       time = (Tools.getNow() - time)/1000D;
	       printWriter.println("<br><b>Done!<br>Time left: "+time+" sec</b>");
	       printWriter.println("<br>Archive filename: <b>"+fileName+"</b>");
	   }
	   catch (Exception e)
		{
	   	sk.iway.iwcm.Logger.error(e);
		}
	}


	private void getFilesFromDir(String mainDir, String basePath, ServletContext servletContext, ZipOutputStream zipOut, PrintWriter printWriter)
	{
		byte[] buf = new byte[BUFFER];
		FileInputStream in;
		int scrollIndex = 0;

		if (mainDir.endsWith("/"))
		{
			mainDir = mainDir.substring(0, mainDir.length()-1);
		}
		//Logger.println(OfflineAction.class,"mainDir: "+mainDir);

		String realPath = mainDir;
		if (realPath != null)
		{
			File file = new File(realPath);
			int size;
			int i;

			try
			{
				Logger.println(OfflineAction.class,"file name: "+file.getAbsolutePath());
				if (file.isDirectory())
				{
					File[] files = file.listFiles();
					size = files.length;
					for (i=0; i<size; i++)
					{
						file = files[i];

						if (file.isDirectory())
						{
							if (!file.getName().equalsIgnoreCase("cvs"))
							{
								getFilesFromDir(mainDir+"/"+file.getName(), basePath, servletContext, zipOut, printWriter);
							}
						}
						else
						{
							if (file.getName().toLowerCase().indexOf(".cvsignore") == -1 && !file.getName().toLowerCase().endsWith(".zip"))
							{
								if (printWriter != null)
								{
									printWriter.println("Adding file: "+file.getName()+"<br>");
									if (scrollIndex >= 15)
									{
										printWriter.println("<script language='javascript'>window.scrollBy(0,1000);</script>");
										scrollIndex = 0;
									}
									printWriter.flush();
									scrollIndex++;
								}

								if (file.getName().toLowerCase().indexOf(".css") != -1)
								{
									StringBuilder sb = new StringBuilder();
									String data = null;
									in = new FileInputStream(file);
									InputStreamReader inStr = new InputStreamReader(in, Constants.FILE_ENCODING);

									char[] buffer = new char[8000];
									int n = 0;
									while (true)
									{
										 n = inStr.read(buffer);
										 if (n < 1) break;
										 sb.append(buffer, 0, n);
									}
									in.close();
									inStr.close();
									data = sb.toString();

									//uprav cesty
									data = fixPath(data, basePath);

									//Add ZIP entry to output stream.
									zipOut.putNextEntry(new ZipEntry(file.getAbsolutePath()));

					            // Transfer bytes from the file to the ZIP file
									zipOut.write(data.getBytes());

								}
								else
								{
									in = new FileInputStream(realPath + File.separatorChar + file.getName());

									//Add ZIP entry to output stream.
									zipOut.putNextEntry(new ZipEntry(file.getAbsolutePath()));

					            // Transfer bytes from the file to the ZIP file
					            int len;
					            while ((len = in.read(buf)) > 0)
					            {
					            	zipOut.write(buf, 0, len);
					            }
								}
				            // Complete the entry
				            zipOut.closeEntry();
				            in.close();
							}
						}
					}
				}
			}
			catch (Exception e)
			{
				sk.iway.iwcm.Logger.error(e);
			}
		}


	}

	/**
	 * Stiahnutie stranky s vyuzitim Jakarta HTTP Client (zvlada POST aj GET)
	 * nemoze sa pouzit nastroj z Tools, tu su customizovane hlavicky
	 * @param basePath
	 * @param req
	 * @return
	 */
	public static String downloadUrl(String basePath, HttpServletRequest req)
	{
		//aby nas neodhlasilo
		if (basePath.contains("/logoff.do")) return "";

		String data = null;

		HttpClient client = HttpClientBuilder.create().setSSLContext(Tools.doNotVerifyCertificates(null)).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build();

		Logger.println(OfflineAction.class,basePath);
		String name;
        String value;

	   HttpGet method = new HttpGet(basePath);

      Enumeration<String> e = req.getHeaderNames();
		while (e.hasMoreElements())
		{
			name = e.nextElement();
			value = req.getHeader(name);
			if ("host".equalsIgnoreCase(name))
			{
				//value = "www2.ing.cz";
				try
				{
					URL url = new URL(basePath);
					value = url.getHost();
				}
				catch (Exception ex)
				{

				}
			}
			if ("User-Agent".equalsIgnoreCase(name))
			{
				value = value + "; WebJET Offline"; //NOSONAR
			}
			method.setHeader(name, value);
			Logger.println(OfflineAction.class,name+": "+value);
		}
		method.setHeader("userInServletContext", "true");
		//nastav dmail header, aby sa negeneroval inline editor
		method.setHeader("dmail", "1");

		String contentType = req.getContentType()+"; charset="+req.getCharacterEncoding();
		method.setHeader("Content-Type", contentType);
		Logger.println(OfflineAction.class,"header: Content-Type: " + contentType);

		try {
			HttpResponse response = client.execute(method);

			// write out the response headers
			Logger.println(OfflineAction.class,"*** Response ***");
			Logger.println(OfflineAction.class,"Status Line: " + response.getStatusLine());
			Header[] responseHeaders = response.getAllHeaders();
			Header h;
			String location = null;
			for (int i=0; i<responseHeaders.length; i++)
			{
				h = responseHeaders[i];
				//Logger.print(this,responseHeaders[i]);
				if (h.getName()!=null && h.getValue() != null)
				{
					Logger.println(OfflineAction.class,h.getName()+": "+h.getValue());
					if (h.getName().equalsIgnoreCase("Location"))
					{
						location = h.getValue();
					}
					//res.setHeader(h.getName(), h.getValue());
				}
			}

			if (location == null)
			{
				try
				{
					StringBuilder sb = new StringBuilder();
					BufferedInputStream is = new BufferedInputStream(response.getEntity().getContent());
					InputStreamReader in = new InputStreamReader(is, SetCharacterEncodingFilter.getEncoding());
					char[] buffer = new char[8000];
					int n = 0;
					while (true)
					{
						 n = in.read(buffer);
						 if (n < 1) break;
						 sb.append(buffer, 0, n);
					}
					in.close();
					data = sb.toString();
				}
				catch (IOException ex)
				{
					sk.iway.iwcm.Logger.error(ex);
				}
			}
			else
			{
				try
				{
					//	uprav location
					String baseHref = Tools.getBaseHrefLoopback(req);
					if (location.startsWith(baseHref))
					{
						location = location.substring(location.indexOf('/', 9));
					}
					int start = location.indexOf(";jsessionid");
					if (start !=-1)
					{
						int end = location.indexOf('?', start);
						if (end > start)
						{
							location = location.substring(0, start) + location.substring(end);
						}
						else
						{
							location = location.substring(0, start);
						}
					}
					data = "<html><body><a href='"+location+"'>"+location+"</a><script language='JavaScript'>window.location.href='"+location+"';</script></body></html>";
				}
				catch (Exception ex)
				{
					data = "<html><body><a href='"+location+"'>"+location+"</a><script language='JavaScript'>window.location.href='"+location+"';</script></body></html>";
				}
			}
		} catch (Exception ex) {
			Logger.error(OfflineAction.class, ex);
		}


		//clean up the connection resources
		method.releaseConnection();
		//method.recycle();

		//Logger.println(OfflineAction.class,data);

		return(data);
	}
}