1 package org
.argeo
.cms
.internal
.http
;
3 import static javax
.jcr
.Property
.JCR_DESCRIPTION
;
4 import static javax
.jcr
.Property
.JCR_LAST_MODIFIED
;
5 import static javax
.jcr
.Property
.JCR_TITLE
;
6 import static org
.argeo
.cms
.CmsTypes
.CMS_IMAGE
;
8 import java
.io
.IOException
;
9 import java
.io
.PrintWriter
;
10 import java
.net
.MalformedURLException
;
12 import java
.security
.PrivilegedExceptionAction
;
13 import java
.util
.Calendar
;
14 import java
.util
.Collection
;
16 import javax
.jcr
.Node
;
17 import javax
.jcr
.NodeIterator
;
18 import javax
.jcr
.Repository
;
19 import javax
.jcr
.RepositoryException
;
20 import javax
.jcr
.Session
;
21 import javax
.security
.auth
.Subject
;
22 import javax
.security
.auth
.login
.LoginContext
;
23 import javax
.security
.auth
.login
.LoginException
;
24 import javax
.servlet
.ServletException
;
25 import javax
.servlet
.http
.HttpServlet
;
26 import javax
.servlet
.http
.HttpServletRequest
;
27 import javax
.servlet
.http
.HttpServletResponse
;
29 import org
.argeo
.cms
.CmsException
;
30 import org
.argeo
.jcr
.JcrUtils
;
31 import org
.argeo
.node
.NodeConstants
;
32 import org
.argeo
.node
.NodeUtils
;
33 import org
.osgi
.framework
.BundleContext
;
34 import org
.osgi
.framework
.FrameworkUtil
;
35 import org
.osgi
.framework
.ServiceReference
;
37 class LinkServlet
extends HttpServlet
{
38 private final BundleContext bc
= FrameworkUtil
.getBundle(getClass()).getBundleContext();
40 private static final long serialVersionUID
= 3749990143146845708L;
43 protected void service(HttpServletRequest request
, HttpServletResponse response
)
44 throws ServletException
, IOException
{
45 String path
= request
.getPathInfo();
46 String userAgent
= request
.getHeader("User-Agent").toLowerCase();
47 boolean isBot
= false;
48 // boolean isCompatibleBrowser = false;
49 if (userAgent
.contains("bot") || userAgent
.contains("facebook") || userAgent
.contains("twitter")) {
52 // else if (userAgent.contains("webkit") ||
53 // userAgent.contains("gecko") || userAgent.contains("firefox")
54 // || userAgent.contains("msie") || userAgent.contains("chrome") ||
55 // userAgent.contains("chromium")
56 // || userAgent.contains("opera") || userAgent.contains("browser"))
58 // isCompatibleBrowser = true;
62 // log.warn("# BOT " + request.getHeader("User-Agent"));
63 canonicalAnswer(request
, response
, path
);
67 // if (isCompatibleBrowser && log.isTraceEnabled())
68 // log.trace("# BWS " + request.getHeader("User-Agent"));
69 redirectTo(response
, "/#" + path
);
72 private void redirectTo(HttpServletResponse response
, String location
) {
73 response
.setHeader("Location", location
);
74 response
.setStatus(HttpServletResponse
.SC_FOUND
);
77 // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
78 // String userAgent = request.getHeader("User-Agent").toLowerCase();
79 // return userAgent.startsWith("facebookexternalhit/");
82 /** For bots which don't understand RWT. */
83 private void canonicalAnswer(HttpServletRequest request
, HttpServletResponse response
, String path
) {
84 Session session
= null;
86 PrintWriter writer
= response
.getWriter();
87 session
= Subject
.doAs(anonymousLogin(), new PrivilegedExceptionAction
<Session
>() {
90 public Session
run() throws Exception
{
91 Collection
<ServiceReference
<Repository
>> srs
= bc
.getServiceReferences(Repository
.class,
92 "(" + NodeConstants
.CN
+ "=" + NodeConstants
.NODE
+ ")");
93 Repository repository
= bc
.getService(srs
.iterator().next());
94 return repository
.login();
98 Node node
= session
.getNode(path
);
99 String title
= node
.hasProperty(JCR_TITLE
) ? node
.getProperty(JCR_TITLE
).getString() : node
.getName();
100 String desc
= node
.hasProperty(JCR_DESCRIPTION
) ? node
.getProperty(JCR_DESCRIPTION
).getString() : null;
101 Calendar lastUpdate
= node
.hasProperty(JCR_LAST_MODIFIED
) ? node
.getProperty(JCR_LAST_MODIFIED
).getDate()
103 String url
= getCanonicalUrl(node
, request
);
104 String imgUrl
= null;
105 loop
: for (NodeIterator it
= node
.getNodes(); it
.hasNext();) {
106 // Takes the first found cms:image
107 Node child
= it
.nextNode();
108 if (child
.isNodeType(CMS_IMAGE
)) {
109 imgUrl
= getDataUrl(child
, request
);
113 StringBuilder buf
= new StringBuilder();
114 buf
.append("<html>");
115 buf
.append("<head>");
116 writeMeta(buf
, "og:title", escapeHTML(title
));
117 writeMeta(buf
, "og:type", "website");
118 buf
.append("<meta name='twitter:card' content='summary' />");
119 buf
.append("<meta name='twitter:site' content='@argeo_org' />");
120 writeMeta(buf
, "og:url", url
);
122 writeMeta(buf
, "og:description", escapeHTML(desc
));
124 writeMeta(buf
, "og:image", imgUrl
);
125 if (lastUpdate
!= null)
126 writeMeta(buf
, "og:updated_time", Long
.toString(lastUpdate
.getTime().getTime()));
127 buf
.append("</head>");
128 buf
.append("<body>");
129 buf
.append("<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
130 .append(path
).append("'>").append(escapeHTML(title
)).append("</a> instead.</b></p>");
131 writeCanonical(buf
, node
);
132 buf
.append("</body>");
133 buf
.append("</html>");
134 writer
.print(buf
.toString());
136 response
.setHeader("Content-Type", "text/html");
138 } catch (Exception e
) {
139 throw new CmsException("Cannot write canonical answer", e
);
141 JcrUtils
.logoutQuietly(session
);
146 * From http://stackoverflow.com/questions/1265282/recommended-method-for-
147 * escaping-html-in-java (+ escaping '). TODO Use
148 * org.apache.commons.lang.StringEscapeUtils
150 private String
escapeHTML(String s
) {
151 StringBuilder out
= new StringBuilder(Math
.max(16, s
.length()));
152 for (int i
= 0; i
< s
.length(); i
++) {
153 char c
= s
.charAt(i
);
154 if (c
> 127 || c
== '\'' || c
== '"' || c
== '<' || c
== '>' || c
== '&') {
162 return out
.toString();
165 private void writeMeta(StringBuilder buf
, String tag
, String value
) {
166 buf
.append("<meta property='").append(tag
).append("' content='").append(value
).append("'/>");
169 private void writeCanonical(StringBuilder buf
, Node node
) throws RepositoryException
{
171 if (node
.hasProperty(JCR_TITLE
))
172 buf
.append("<p>").append(node
.getProperty(JCR_TITLE
).getString()).append("</p>");
173 if (node
.hasProperty(JCR_DESCRIPTION
))
174 buf
.append("<p>").append(node
.getProperty(JCR_DESCRIPTION
).getString()).append("</p>");
175 NodeIterator children
= node
.getNodes();
176 while (children
.hasNext()) {
177 writeCanonical(buf
, children
.nextNode());
179 buf
.append("</div>");
183 private StringBuilder
getServerBaseUrl(HttpServletRequest request
) {
185 URL url
= new URL(request
.getRequestURL().toString());
186 StringBuilder buf
= new StringBuilder();
187 buf
.append(url
.getProtocol()).append("://").append(url
.getHost());
188 if (url
.getPort() != -1)
189 buf
.append(':').append(url
.getPort());
191 } catch (MalformedURLException e
) {
192 throw new CmsException("Cannot extract server base URL from " + request
.getRequestURL(), e
);
196 private String
getDataUrl(Node node
, HttpServletRequest request
) throws RepositoryException
{
198 StringBuilder buf
= getServerBaseUrl(request
);
199 buf
.append(NodeUtils
.getDataPath(NodeConstants
.NODE
, node
));
200 return new URL(buf
.toString()).toString();
201 } catch (MalformedURLException e
) {
202 throw new CmsException("Cannot build data URL for " + node
, e
);
206 // public static String getDataPath(Node node) throws
207 // RepositoryException {
208 // assert node != null;
209 // String userId = node.getSession().getUserID();
210 //// if (log.isTraceEnabled())
211 //// log.trace(userId + " : " + node.getPath());
212 // StringBuilder buf = new StringBuilder();
213 // boolean isAnonymous =
214 // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS);
216 // buf.append(WEBDAV_PUBLIC);
218 // buf.append(WEBDAV_PRIVATE);
219 // Session session = node.getSession();
220 // Repository repository = session.getRepository();
222 // if (repository.isSingleValueDescriptor(NodeConstants.CN)) {
223 // cn = repository.getDescriptor(NodeConstants.CN);
225 //// log.warn("No cn defined in repository, using " +
226 // NodeConstants.NODE);
227 // cn = NodeConstants.NODE;
230 // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath())
234 private String
getCanonicalUrl(Node node
, HttpServletRequest request
) throws RepositoryException
{
236 StringBuilder buf
= getServerBaseUrl(request
);
237 buf
.append('/').append('!').append(node
.getPath());
238 return new URL(buf
.toString()).toString();
239 } catch (MalformedURLException e
) {
240 throw new CmsException("Cannot build data URL for " + node
, e
);
242 // return request.getRequestURL().append('!').append(node.getPath())
246 private Subject
anonymousLogin() {
247 Subject subject
= new Subject();
250 lc
= new LoginContext(NodeConstants
.LOGIN_CONTEXT_ANONYMOUS
, subject
);
253 } catch (LoginException e
) {
254 throw new CmsException("Cannot login as anonymous", e
);