Pay to Roll Dice Game

In my recent dive into the world of the Lightning Network, I built a simple project—a React-based betting game. During the project, I learned a couple of things that I’d like to share in this post.

First of all, I was surprised by how supportive ChatGPT is from a technical point of view. It helped me scaffold the project, recommended specific technologies, and even suggested an appropriate editor. On the other hand, I was unpleasantly surprised by the Lightning Network—how unintuitive it still is from the user’s perspective. But first things first.

I had a rough idea for a one-evening project built on the Lightning Network, so I asked ChatGPT to generate a list of ideas. I chose a dice roll game where:

  • Players bet on a number by making an LN payment.
  • If they win, they take the entire pot by claiming it with LN.
  • If they don’t win, their sats contribute to the growing pot for the next round.

Simple enough—but since I didn’t have any experience with these kinds of technologies, I had to rely on ChatGPT again. It proposed using React and Vite as frontend frameworks, Alby or LNbits for integrating Lightning payments, Supabase or Firebase as a minimal backend solution, and Vercel, Replit, or Netlify for deployment.

Since I wanted to use a mobile wallet instead of a browser extension, the payment solution was narrowed down to LNbits. I also stripped out the need for a backend for the MVP and stuck with React useState only.

Bootstrapping the project was very easy. It basically involved installing a few dependencies via npm and adding an admin key for LNbits. That could be a security issue without proxying the calls through a backend, but I decided to accept the risk for the MVP and the small satoshi amounts in the demo wallet.

After picking a lightweight editor (I went with Zed since I didn’t want to use VSCode or WebStorm), we had to do a couple of iterations over the code to get it running and eliminate all the linting complaints. But it was running and accepting payments—all within an hour! One missing piece was adding support for claiming winnings, but we quickly solved that as well.

Here I’d like to pause: the user experience while using Lightning payments is still quite harsh, and I feel that’s something preventing broader adoption by regular users. I had been using the Phoenix wallet for a while—but only for sending payments. Now I needed the players to receive payments as well, which meant gaining a basic understanding of how Lightning actually works, including generating BOLT11 invoices. That’s quite easy with Phoenix, but still, the user has to request a specific amount—raising questions like, “What if I request too little or too much?”

So I decided to implement LNURL-withdraw, which removes the need for players to create invoices. After a bit of work, I had it running—players could now claim winnings by simply scanning a QR code! Again, not super intuitive, but functional. I can imagine a future where wallets have a single button (e.g., “Scan QR”) for both payments and withdrawals. That would improve the user experience dramatically. But with Phoenix, the user has to first tap “Receive” and then click a tiny icon in the corner—not very user-friendly.

Anyway, let’s move on. I had it running locally, linted, code-reviewed, and ready to deploy. From the options ChatGPT suggested (Replit, Netlify, Vercel), I chose Vercel (based purely on gut feeling, sorry 😄). It connected to the project’s GitHub repo and was able to fetch and deploy the code on demand or on push to a specific branch. So easy! That means the project is public and running—though it does have some security issues without a backend. But that’s a story for another time, if I ever get to it.

Conclusion

I used generative AI for ideating, implementing, and deploying a simple project. I was surprised by how easy it was—originally I wanted a weekend project, but I actually managed to finish it in a single evening. I can now easily imagine no-code or multimodal programming becoming a reality.

Still, user input is necessary—at least for reviewing, refining incorrect URL calls, and challenging odd bits of generated code (linting alone isn’t enough). As for the Lightning Network, I was disappointed by how unintuitive it still is, especially considering it aims for mass adoption. Perhaps it’s not the right protocol for widespread Bitcoin use—yet. Looking ahead, I hope it matures to the point where, like the internet today, people can use it without needing to understand the ISO/OSI model. The future looks bright!

MVC and Spring surf

One of things I missed in Spring Surf are Models and Controllers. I tried many ways to get them working, but still no useful solution. So I chose really simple way to do that – to define another dispatcher and to play with urlrewrite. In this post I’ll describe what I did and as a … Pokračovat ve čtení „MVC and Spring surf“

One of things I missed in Spring Surf are Models and Controllers. I tried many ways to get them working, but still no useful solution. So I chose really simple way to do that – to define another dispatcher and to play with urlrewrite. In this post I’ll describe what I did and as a little bonus you’ll find a breathtaking example 🙂 – YUI based Ajax whisperer.

Let’s start with empty Spring Surf skeleton:

project --topLevelPackage cz.shmoula.controllers
surf install
perform eclipse

Now add dispatcher – in web.xml we have to define new servlet with name controller, loaded as second and with url-pattern /app (default dispatcher is mapped to /service), which is filtered by urlrewrite.

<servlet>
  <servlet-name>controller</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>controller</servlet-name>
  <url-pattern>/app/*</url-pattern>
</servlet-mapping>

In next step we’ll need web application config – Spring names them as -servlet.xml prefixed with servlet name. So let’s create controller-servlet.xml in WEB-INF (in STS New/Spring bean configuration file) and add some useful stuff there: for first we want to use annotations for our controllers, so let’s add there two beans – DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdapter.

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

Then we need to define where are our controllers lying, so let’s define base package.

<context:component-scan base-package="cz.shmoula.controllers.controller" use-default-filters="false">
  <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation" />
</context:component-scan>

Last thing is to tell, where will be our views and which suffix they use.

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/jsp/" />
  <property name="suffix" value=".jsp" />
</bean>

Okay, folks, that’s almost all! Now the interesting part: Writing the controller. Make a class in package we defined earlier. It should extend some AbstractController or another stuff, but it’s not necessary, we’ll simple it! 😉 Put a test method inside:

package cz.shmoula.controllers.controller;

@Controller
public class AutocompleteController{

  @RequestMapping(value = "/test")
  public String test(Model model){
    model.addAttribute("attribute", "Some sample string");
    return "test";
  }
}

That’s our controller. Now we need a view. Previously we defined parameters for view resolver and in controller we’re returning name of our model. Putted together result is: /WEB-INF/jsp/test.jsp

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Test page backed by controller</title>
  </head>
  <body>
    <h1>Hi, what we have there?</h1>
    <p>${attribute}</p>
  </body>
</html>

Last thing to do is to define some rules for urlrewrite. We’ll try to forward all /app/ requests to our dispatcher and anything else to Default Spring Dispatcher. That’s done by these rules (urlrewrite.xml):

<rule>
  <from>/app/(.*)</from>
  <to>/app/$1</to>
</rule>
<rule>
  <from>/</from>
  <to>/service/</to>
</rule>

Done? So let’s test it! Run it up and open browser at http://localhost:8080/controllers/app/test. Does it work? I hope so!

Test page
Test page

That was the first part – worst one, now came pure fun – some functionality. We want to implement some buzzlike thingy as Autocompletion is – whisperer. To keep it simple our model layer will be just a Collection with Strings, so let’s modify our controller with constructor:

private Collection<String> termList = new HashSet<String>();
public AutocompleteController() {
    termList.add("Prague");
    termList.add("Paris");
    termList.add("Poznan");
}

Now we need controller code – something returning some results. So at /autocomplete will be routine, which waits for „query“parameter with part of String, which tries to find in termList and return:

@RequestMapping(value = "/autocomplete")
public void autocomplete(HttpServletRequest httpRequest, Model model) {
    String query = httpRequest.getParameter("query");
    String response = "";

    if (query != null) {
        for (Iterator<String> i = termList.iterator(); i.hasNext();) {
            String term = i.next();
            if (term.startsWith(query))
                response += "n" + term;
        }
    }
    model.addAttribute("response", response);

}

Now we have to render our model through something, so add view /WEB-INF/jsp/autocomplete.jsp:

${response }

And now to add all that into widget. Let’s extend calendar widget in /WEB-INF/webscripts/calendar – first add styles and YUI scripts into calendar.get.head.ftl:

<link type="text/css" rel="stylesheet" href="http://yui.yahooapis.com/2.8.2r1/build/autocomplete/assets/skins/sam/autocomplete.css">
<script src="http://yui.yahooapis.com/2.8.2r1/build/yahoo-dom-event/yahoo-dom-event.js"></script>
<script src="http://yui.yahooapis.com/2.8.2r1/build/datasource/datasource-min.js"></script>
<script src="http://yui.yahooapis.com/2.8.2r1/build/connection/connection-min.js"></script>
<script src="http://yui.yahooapis.com/2.8.2r1/build/autocomplete/autocomplete-min.js"></script>

<style type="text/css">
    #myAutoComplete {
        width:25em;
        padding-bottom:2em;
    }
</style>

The last step is to create div and put a script into calendar.get.html.ftl:

<div>
    <label for="myInput">Autocomplete:</label>
    <div id="myAutoComplete">
        <input id="myInput" type="text">
        <div id="myContainer"></div>
    </div>
</div>
<script type="text/javascript">
  YAHOO.example.BasicRemote = function() {
    var oDS = new YAHOO.util.XHRDataSource("/controllers/app/autocomplete");
    oDS.responseType = YAHOO.util.XHRDataSource.TYPE_TEXT;
    oDS.responseSchema = {
      recordDelim: "n",
      fieldDelim: "t"
    };
    oDS.maxCacheEntries = 5;
    var oAC = new YAHOO.widget.AutoComplete("myInput", "myContainer", oDS);
    return {
      oDS: oDS,
      oAC: oAC
    };
  }();
</script>

And that should be all, now let’s test it, if it works! So open http://localhost:8080/controllers/calendar and voila…

Working autocompletion
Working autocompletion

Ok, that’s all for this post, looks like it works! So you can download backup of this code somewhere around here and enjoy it on your own devbox. Keep looking forward for next stuff, maybe using facebook login with Spring social, who knows ;-).

JSP based components in Spring Surf

In my last blogpost we got Java backed webscripts working, now let’s try something completely different: defining page snippet as a component in .JSP. Just let’s continue with our last code, which is possible to download, but it’s not necessary, it’s possible to build it all on green meadow (aka after project in roo is … Pokračovat ve čtení „JSP based components in Spring Surf“

In my last blogpost we got Java backed webscripts working, now let’s try something completely different: defining page snippet as a component in .JSP. Just let’s continue with our last code, which is possible to download, but it’s not necessary, it’s possible to build it all on green meadow (aka after project in roo is created and surf installed).

First thing to do is to add dependency for jstl into Maven’s pom.xml and then update maven dependencies and refresh our project in Eclipse.

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>com.springsource.javax.servlet.jsp.jstl</artifactId>
    <version>1.2.0</version>
</dependency>

Now you can get glue on Spring forums, or trust me and create new component type with JSP Renderer: it’s needed to create new directory – /WEB-INF/classes/surf/site/component-types and inside new component type configuration – test.xml.

<?xml version="1.0" encoding="UTF-8"?>
<component-type>
    <id>test</id>
    <title>Test Component Type</title>
    <description>Test Component Type</description>

    <processor mode="view">
        <id>jsp</id>
        <jsp-path></jsp-path>
    </processor>
</component-type>

Jsp-path is at this time undefined, because we haven’t created any page yet. So let’s do something with that. We need to create new template and new template instance:

roo> surf template create --path test
Created SRC_MAIN_WEBAPP/WEB-INF/templates/test.ftl
roo> surf template instance create --id test --path templates --template test
Created SRC_MAIN_WEBAPP/WEB-INF/templates/test.xml

At this time we have Freemarker template (test.ftl) and template instance (test.xml) inside directory /WEB-INF/templates. We need to edit those files. First let’s add following code into test.ftl. There are four regions defined – three of them inside template scope (already defined by installation of Surf addon) and one with page scope, which we’ll define and bind later.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>${page.title}</title>
  </head>

  <body>
    <div id="page">
      <div id="header">
        <@region id="header" scope="template" />
      </div>
      <div id="horznav">
        <@region id="horznav" scope="template" />
      </div>
      <div id="content">
        <@region id="content" scope="page" />
      </div>
      <div id="footer">
        <@region id="footer" scope="template" />
      </div>
    </div>
  </body>
</html>

Following code (template instance) is definition of components in template scope and is also located in templates directory: test.xml.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<template-instance>
  <title>test</title>
  <description>Instance of test</description>
  <template-type>test</template-type>
  <components>
    <component>
      <region-id>header</region-id>
      <url>/company/header</url>
    </component>
    <component>
      <region-id>horznav</region-id>
      <url>/navigation/horizontal</url>
    </component>
    <component>
      <region-id>footer</region-id>
      <url>/company/footer</url>
    </component>
  </components>
</template-instance>

Now we can create new page based on previously defined template-instance and add link to our new page to horizontal bar. For those actions we can use roo:

roo> surf page create --id test --path pagestest --templateInstance test
Created SRC_MAIN_WEBAPP/WEB-INF/pages/test
Created SRC_MAIN_WEBAPP/WEB-INF/pages/test/test.xml

roo> surf page association create --sourceId home --destId test
Managed SRC_MAIN_WEBAPP/WEB-INF/pages/home/home.xml

As written in roo output, directory test inside pages is created and test.xml within. This file is page configuration and we have to define our content for region defined in template. But not necessary, it’s good idea to check our work now. So let’s try to Run it on server… It’s ugly, but it works! 🙂

First try
First try

That’s cool, but our aim is to create working .jsp page. How to do that? What about simply create that page? 🙂 So let’s create test.jsp inside /WEB-INF/pages/test and fill it with some simple code:

<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core_rt'%>

<%@ page import="java.util.*"  %>
<%@ page contentType="text/html;charset=UTF-8" language="java"%>

<h1>Test jsp</h1>

<%
  String someText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";

  List<String> l = new ArrayList<String>();
  l.add("Maecenas iaculis odio id eros fermentum non tempus erat interdum.");
  l.add("Sed mi orci, dignissim vitae cursus hendrerit, congue sed odio. ");
  l.add("Vivamus quam diam, dapibus sit amet placerat a, tempus at magna.");

  session.setAttribute("someText", someText);
  session.setAttribute("l", l);
%>

<p>${someText }</p>

<c:forEach var="x" items="${l }" >
    <p>${x}</p>
</c:forEach>

That’s our page. Now we need to connect it with previously created component type – /WEB-INF/classes/surf/site/component-types/test.xml. In time of creation we omit jsp-path, so fill it up now:

<jsp-path>/WEB-INF/pages/test/test.jsp</jsp-path>

Last thing to do is to put our shiny new component-type into page. In template we defined region, which is filled in page scope. That’s that components section in page config – /WEB-INF/pages/test/test.xml. It’s good idea to add some title and some styling like border and background thanks to chrome element:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<page>
  <id>test</id>
  <title>test</title>
  <template-instance>test</template-instance>
  <authentication>none</authentication>
  <components>
    <component>
      <region-id>content</region-id>
      <component-type-id>test</component-type-id>
      <chrome>box</chrome>
      <title>JSP inside</title>
    </component>
  </components>
</page>

After rebuild and redeploy we have our jsp component working, as you can see at http://localhost:8080/webscripts/test. Congratulations, good work!

Final application look
Final application look

Ah, in case you got lost, there is package with working sourcecode for this whole example.

Freemarker templates and java backed webscripts

Wondering about output to freemarker template from java backed webscript in Spring Surf? You’re on the right place, it’s simple so I’ll try to describe it on a few lines. For first I suppose some prerequisites you have: you have Spring Roo and (or) Springsource Tool Suite (or Eclipse with plugin), you have Spring surf … Pokračovat ve čtení „Freemarker templates and java backed webscripts“

Wondering about output to freemarker template from java backed webscript in Spring Surf? You’re on the right place, it’s simple so I’ll try to describe it on a few lines. For first I suppose some prerequisites you have:

Ok, let’s get it done quickly! Create new directory, for example webscripts. Go into it, fire up roo, create a new project, install surf extension into it and create an Eclipse project:

project --topLevelPackage cz.shmoula.webscripts
surf install
perform eclipse

Now you should have base skeleton of our future app (you can also download it here) and you can import it into SpringSource Tool Suite.

Webscripts are located in src/main/webapp/WEB-INF/webscripts folder, so open that folder and create two files: example.get.html.ftl (template) and example.get.desc.xml (description).

<p>logged user: ${userId}</p>
<webscript>
      <shortname>Example</shortname>
      <description>Sample java backed webscript</description>
      <url>/example</url>
</webscript>

Now we have to create our java source code. It’s not bad idea to put webscripts to their own package, for example webscript. In that package create a new class, which extends org.springframework.extensions.webscripts.AbstractWebScript – Example. By doing that we have to implement an execute method, which is called, when webscript is executed:

@Override
public void execute(WebScriptRequest req, WebScriptResponse res)
    throws IOException {
}

Let’s fill that method. We need a model map, which holds values as pair (String)key – (Object)value. For our desired function we also need to get HttpServletRequest:

Map<String, Object> model = new HashMap<String, Object>();
HttpServletRequest httpRequest = ServletUtil.getRequest();

Now to fill up model (aka our function). Let’s find out if there is logged in user and if so, write out it’s userId:

if(AuthenticationUtil.isAuthenticated(httpRequest)){
      model.put("userId", AuthenticationUtil.getUserId(httpRequest));
}else{
      model.put("userId", "guest");
}

Final step is to put out our model. We need to get writer and renderTemplate, which we specified by its path (and also flush and close our writer):

Writer writer = res.getWriter();
String templatePath = "webscripts/example.get.html.ftl";
renderTemplate(templatePath, createTemplateParameters(req, res, model), writer);

writer.flush();
writer.close();

Last thing we have to do is to let Surf know about our shiny new webscript. You can add new bean directly inside applicationContext, but it’s not bad idea to keep them separately. So in src/main/webapp/WEB-INF/config create new Spring Bean Definition file – custom-webscripts-config.xml and put inside new bean definition:

<bean id="webscript.webscripts.example.get" class="cz.shmoula.webscript.Example" parent="webscript"/>

Now we have to import that new config: in the same directory is surf-config.xml file, so let’s add there new import:

<import resource="custom-webscripts-config.xml"/>

At this time we can run our project and visit http://localhost:8080/webscripts/example, which shows following page:

Image and video hosting by TinyPic

It’s possible to login (if you have set up user factory) as an Alfresco user and after that another userId is shown. I’m planning to write new blogpost about custom user factory and stuff like that, so be patient please. Finally, you can download source code for this example at this link. Structure of project is shown at following screenshot (note that red dots – those are modified files):

Image and video hosting by TinyPic

Finally, try to experiment with some examples from Alfresco wiki and look forward to next blogpost.

HOWTO Alfresco PHP Library and xUbuntu

I tried to install Alfresco PHP Library and install it on my kubuntu developing box and it's really very easy to get it running! Instalation procedure for windows is described in Alfresco wiki, but there are some differencies.

I tried to install Alfresco PHP Library and install it on my kubuntu developing box and it’s really very easy to get it running! Instalation procedure for windows is described in Alfresco wiki, but there are some differencies.

To get it running it’s needed to install apache and php:

sudo apt-get install apache2 libapache2-mod-php5

Then you need to download PHP Library from Sourceforge. After that you can unpack it somewhere; good place is for example /usr/share/php/alfresco-php-library. Now it’s not bad to set virtual directory to that directory. So edit your /etc/apache2/conf.d/alias file and add something like this:

Alias /Alfresco/ /usr/share/php/alfresco-php-library/

<Directory /usr/share/php/alfresco-php-library/>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
Allow from all
</Directory>

Also including this directory automaticaly (/etc/php5/apache2/php.ini) should be cool:

include_path = „.:/usr/share/php/alfresco-php-library“

Now you need to restart apache:

/etc/init.d/apache2 restart

And edit configuration file in alfresco-php-library/Examples/config.php.

$repositoryUrl = "http://localhost:8080/alfresco/api";
$userName = "admin";
$password = "admin";

After all that you can try to open web browser and point it to http://localhost/Alfresco/Examples/SimpleBrowse/ and it should work 😎


Image and video hosting by TinyPic

Sending content to java webscript via POST method

Yesterday I stucked on problem of getting data from WebScriptRequest, which were sent by POST method. There is some bug in Apache Tomcat, so there is a simple workaround described for example here. My task was to create webscript, which tries to find a file in repository (or create it, if it doesn't exist) and to write content inside – data of POST request.

Yesterday I stucked on problem of getting data from WebScriptRequest, which were sent by POST method. There is some bug in Apache Tomcat, so there is a simple workaround described for example here. My task was to create webscript, which tries to find a file in repository (or create it, if it doesn’t exist) and to write content inside – data of POST request.

My webscript is Java backed, so only description in WEB-INF/classes/alfresco/templates/client/webscripts/org/alfresco/archivace is needed:

<webscript>
<shortname>SaveModif file</shortname>
<description>Ulozi dorucena data do *.modif.xml</description>
<url>/archivace/savemodif?nodeRef={noderef}</url>
<authentication>user</authentication>
<transaction>required</transaction>
</webscript>

Also definition in web-scripts-application-context.xml:

<bean id=“webscript.org.alfresco.archivace.savemodif.post“ class=“cz.shmoula.webscripts.SaveModif“ parent=“webscript“>
<property name=“authenticationComponent“ ref=“AuthenticationComponent“/>
<property name=“nodeService“ ref=“NodeService“/>
<property name=“searchService“ ref=“SearchService“/>
<property name=“fileFolderService“ ref=“FileFolderService“/>
<property name=“contentService“ ref=“ContentService“/>
</bean>

Now let’s get Node object itself:

// try to get DescriptionNode
Node xmlNode = getDescriptionNode(nodeRef);

// if DescriptionNode doesn’t exist, create one
if (xmlNode == null) {
String path = noda.getName();
String filename = path.substring(0, path.lastIndexOf(„.“)) + „.modif.xml“;
NodeRef spaceRef = nodeService.getPrimaryParent(noda.getNodeRef()).getParentRef();
xmlNode = createDescriptionNode(spaceRef, filename);
}

At this time in xmlNode is Node object – either existing one, or created one, so let’s write that request content inside:

ContentWriter writer = contentService.getWriter(xmlNode.getNodeRef(), ContentModel.PROP_CONTENT, true);
writer.putContent(req.getContent().getInputStream());

Simple, but sometimes it doesnt work. Why? Due to that bug mentioned in perex. So how are data sent through POST method? Let’s try it with wget:

wget –post-data=’pokus‘ –http-user=admin –http-password=admin http://localhost:8080/alfresco/service/archivace/savemodif?nodeRef=workspace://SpacesStore/91919928-3709-4e2e-a829-b0be46976e42

Captured request looks like this

Image and video hosting by TinyPic

As you can see, everything looks ok. But getContent() in our webscript is empty. Why? there is no content – there are just parameters, due to application/x-www-form-urlencoded content type. So req.getParameterNames() returns String[] array in which are in our case two parameters: {{nodeRef}, {pokus}}. This is not what we want, because length ot these parameter names may be limited (2M?). So let’s use mentioned advice – change content-type:

wget –post-data=’pokus‘ –header=’Content-Type: text/xml‘ –http-user=admin –http-password=admin http://localhost:8080/alfresco/service/archivace/savemodif?nodeRef=workspace://SpacesStore/91919928-3709-4e2e-a829-b0be46976e42

And now it works! 🙂 Sniffed request looks like before, just content-type header line had changed:

Image and video hosting by TinyPic

Now is possible to send inputStream of content to Writer:

writer.putContent(req.getContent().getInputStream());

At this time req.getParameterNames() has just one parameter – {{nodeRef}} and content is really saved in data field.

Delete dialog workaround

Write this one, or not to write this blogpost? That is the question :-). But I would like to simply remember for some things i did, so let's do it! In my DMS I have some multimedia content saved and this content has attached description in xml file. Also I have my own space view (more info here), so I show just that multimedia content. In case of deletion that content I want to delete that xml too, so how to do that?

Write this one, or not to write this blogpost? That is the question :-). But I would like to simply remember for some things i did, so let's do it! In my DMS I have some multimedia content saved and this content has attached description in xml file. Also I have my own space view (more info here), so I show just that multimedia content. In case of deletion that content I want to delete that xml too, so how to do that?

First we need to create an action (it's just a copy of original action ;-)) specification in web-client-config and name it – in my case "delete_doc_shm":

<action id="delete_doc_shm">
    <permissions>
        <permission allow="true">Delete</permission>
    </permissions>
    <evaluator>org.alfresco.web.action.evaluator.DeleteDocEvaluator</evaluator>
    <label-id>delete</label-id>
    <image>/images/icons/delete.gif</image>
    <action-listener>#{BrowseSessionBean.deleteFile}</action-listener>
    <params>
        <param name="id">#{actionContext.id}</param>
        <param name="ref">#{actionContext.nodeRef}</param>
    </params>
</action>

It's pretty simple, just a definition of action with Evaluator and wanted permissions. I wrote a blogpost about this a year about :-). Now I have an action, so I need to add it into some group, or view link directly. Action groups are defined in web-client-config too:

<action-group id="session_browse">
    <show-link>false</show-link>
    <style-class>inlineAction</style-class>
    .  .  .
     <action idref="delete_doc_shm" />
</action-group>

And view of this group is realised by actions component (r variable is variable defined by richListComponent, library prefix r is Alfresco's repo taglib):

.  .  .
<r:actions id="actions_menu_1" value="session_browse" context="#{r}" showLink="false" styleClass="inlineAction" />
.  .  .  

The result's like this (there's no difference in original view of that actions menu and this, but functionality – see below ;-)):

Image and video hosting by TinyPic

Now some application logic: In case of pressing our delete butto, BrowseSessionBean.deleteFile() method is called. This method just do same things that original BrowseBean.deleteFile() does, but redirects to my own dialog:

public void deleteFile(ActionEvent event) {
    .  .  .  some foo – bar stuff  .  .  .
    FacesContext fc = FacesContext.getCurrentInstance();
    NavigationHandler navigationHandler = fc.getApplication().getNavigationHandler();
    navigationHandler.handleNavigation(fc, null, "dialog:deleteVcfFile");
}

This dialog is defined in web-client-config like

<dialog
    name="deleteVcfFile"
    page="/jsp/dialog/delete.jsp"
    managed-bean="DeleteVcfContentDialog"
    icon="/images/icons/delete_large.gif"
    title-id="delete_file"
    description-id="delete_file_info" />

and points to DeleteVcfContentDialog bean:

<managed-bean>
    <description>The bean that backs up the Delete Content Dialog</description>
    <managed-bean-name>DeleteVcfContentDialog</managed-bean-name>
    <managed-bean-class>cz.shmoula.dialog.DeleteVcfContentDialog</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
    <managed-property>
        <property-name>navigator</property-name>
        <value>#{NavigationBean}</value>
    </managed-property>
    .  .  .  more properties  .  .  .
</managed-bean>

Class managing this bean extends DeleteContentDialog and has only one method – finishImpl. See following source, it's commented:

public class DeleteVcfContentDialog extends org.alfresco.web.bean.content.DeleteContentDialog {

    @Override
    protected String finishImpl(FacesContext context, String outcome)throws Exception {
        // get the content to delete
        VcfNode node = new VcfNode(this.browseBean.getDocument());
        if (node != null) {
            if (ContentModel.TYPE_MULTILINGUAL_CONTAINER.equals(node.getType())) {
                // delete the mlContainer and its translations
                getMultilingualContentService().deleteTranslationContainer(node.getNodeRef());
            } else {
                // getting attached xml node
                Node descriptionXml = node.getXmlNode();
                // and if xml exists, delete it!
                if(descriptionXml != null)
                    this.getNodeService().deleteNode(descriptionXml.getNodeRef());
                
                // delete the node
                this.getNodeService().deleteNode(node.getNodeRef());
            }
        }
        return outcome;
    }
}

As I can see, it'll be much better to implement some transactions inside this method to 'do some atomicity' 8-). VcfNode is my Node extension, I'll try to describe it in next blogpost.

So, it's pretty easy to workaround system dialogs thanks to Alfresco source code. But sometimes you can find things, which looks like there is no source in sdk 🙁 but google always helps!