Reflex GAE

概要

  • Reflex GAEは、Google App Engine上で効率よくRESTfulアプリを開発するためのライブラリです

機能

  • RequestMapper
    • HTTPリクエストの「プロパティ名=値」の値をBeanのプロパティにセットします。
  • ReflexCoreのFieldMapper
    • JDO updateのためのユーティリティで、Beanのプロパティをsetterを介してJDOにセットします。
  • QueryUtils
    • ParameterBean(Entityと同じ型をした検索用Bean)の値に合致するものをDatastoreから検索します。検索対象の範囲指定(range=100-200など)ができます。
    • JDOQLを使用せず、DatastoreAPIだけを使っているため、自由なIndex構築やPagingによる大規模なデータ処理が可能です。
  • KeyUtils
    • EntityGroupを構成する親子関係のKeyの取得APIなどがあります。
    • EntityBaseのKeyBuilderとKeyの取得、ChildEntityのBuilderとKeyの取得、シーケンス番号をKeyにするAPIがあります。
  • EntityConverter
    • DatastoreAPIを使って検索したときのResult(Properties)をBean(List)に変換します。
    • Comparatorにソート条件を与えることで、検索結果をソートできます。
    • Conditionに条件を与えることで、検索結果をさらに絞り込むことができます。
  • Memcache
    • URLのStreamからbyte配列を取得し、byte配列をMemcacheに格納します。
  • ReflexServlet
    • ユーザアプリのServletはこれをextendsすることで使用できます。
    • getEntityメソッドは、JSONやXMLのリクエストをBeanに変換します
    • doResponseメソッドは、BeanからJSONやXMLに変換してレスポンスを返します。
    • doErrorPageメソッドは、エラーページを返します。
    • doResponseFileメソッドは、ファイルを返します。

ソース

  • Google Code
    • http://reflexworks.googlecode.com/svn/trunk/reflexgae

サンプル

InvoiceServlet

package jp.reflexworks.invoice;

import java.io.IOException;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.JDOException;
import javax.jdo.JDOObjectNotFoundException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jp.reflexworks.gae.servlet.ReflexServlet;
import jp.reflexworks.gae.util.HttpStatus;
import jp.reflexworks.gae.util.RequestMapper;
import jp.reflexworks.gae.util.Status;
import jp.reflexworks.invoice.model.Invoice;
import jp.reflexworks.invoice.model.InvoiceBase;
import jp.reflexworks.invoice.model.Order;
import jp.reflexworks.invoice.util.JdoUtils;

@SuppressWarnings("serial")
public class InvoiceServlet extends ReflexServlet {
        
        Logger logger = Logger.getLogger(this.getClass().getName());
        private static final String MODEL_PACKAGE = "jp.reflexworks.invoice.model";

        public void doGet(HttpServletRequest req, HttpServletResponse resp)
                        throws IOException {
                
                boolean toXml = true;
                if (req.getParameter("xml")!=null) {
                        toXml = false;  // toJSON
                }

                try {
                        Invoice param = new Invoice();
                        (new RequestMapper()).setValue(req, param);
                        
                        JdoUtils jdoUtils = new JdoUtils();
                        
                        String insertCntStr = req.getParameter("init");

                        if (insertCntStr!=null) {
                                
                                String[] idx = null;
                                if (insertCntStr.indexOf("-") > 0) {
                                        idx = insertCntStr.split("-");
                                } else {
                                        idx = new String[2];
                                        idx[0] = "1";
                                        idx[1] = insertCntStr;
                                }

                                List<Invoice> recList = new ArrayList<Invoice>();
                                for (long i = Long.parseLong(idx[0]); i <= Long.parseLong(idx[1]); i++) {
                                        Invoice rec = Instance.newInvoice(i);
                                        recList.add(rec);
                                }
                                jdoUtils.insert(recList);
                                
                                Status status = new Status();
                                status.code = HttpStatus.SC_CREATED;
                                status.message = "Created.";
                                doResponse(resp, status, toXml, MODEL_PACKAGE);
                        }else {

                        String id = req.getParameter("id");
                        // idで検索getEntriesById
                        // invoiceNoで検索 
                        if (id!=null) {
                                Invoice invoice = jdoUtils.getInvoiceById(param);

                                if (invoice == null ) {
                                        Status status = new Status();
                                        status.code = HttpStatus.SC_NO_CONTENT;
                                        status.message = "No data.";
                                        try {
                                                doResponse(resp, status, toXml, MODEL_PACKAGE);
                                        } catch (Exception ee) {
                                                ee.printStackTrace();
                                        }
                                } else {
                                        doResponse(resp, invoice , toXml, MODEL_PACKAGE);       // true:JSON
                                }
                                doResponse(resp, invoice, toXml,MODEL_PACKAGE); // true:JSON
                        }else 
                        if (param.invoiceNo!=null) {
                                Invoice invoice = jdoUtils.getInvoiceByKey(param);
                                invoice.setOrderList(jdoUtils.getOrdersByInvoiceNo(invoice.invoiceNo));
                                doResponse(resp, invoice, toXml,MODEL_PACKAGE); // true:JSON
                        }else {

                        String invoiceNo = req.getParameter("record");
                        if (invoiceNo!=null) {
                                List<Order> recordList = jdoUtils.getOrdersByInvoiceNo(invoiceNo);

                                if (recordList == null || recordList.size() == 0) {
                                        Status status = new Status();
                                        status.code = HttpStatus.SC_NO_CONTENT;
                                        status.message = "No data.";
                                        try {
                                                doResponse(resp, status, toXml, MODEL_PACKAGE);
                                        } catch (Exception ee) {
                                                ee.printStackTrace();
                                        }
                                } else {
                                        Invoice invoice = new Invoice();
                                        invoice.setOrderList(recordList);
                                        
                                        doResponse(resp, invoice , toXml, MODEL_PACKAGE);       // true:JSON
                                }
                        }else {
                                
                                
                                // 各項目で検索

                                (new RequestMapper()).setValue(req, param);
                                InvoiceBase invoiceBase = new InvoiceBase();
                                String pagesize = req.getParameter("pagesize");
                                String nextid = req.getParameter("nextid");
                                invoiceBase.invoiceList = jdoUtils.getEntriesByParam(param, pagesize, nextid);

                                if (invoiceBase.invoiceList == null || invoiceBase.invoiceList.size() == 0) {
                                        Status status = new Status();
                                        status.code = HttpStatus.SC_NO_CONTENT;
                                        status.message = "No data.";
                                        try {
                                                doResponse(resp, status, toXml, MODEL_PACKAGE);
                                        } catch (Exception ee) {
                                                ee.printStackTrace();
                                        }
                                } else {
                                        jdoUtils.complementInvoiceRecord(invoiceBase.invoiceList);
                                        doResponse(resp, invoiceBase , toXml, MODEL_PACKAGE);   // true:JSON
                                }
                        }
                        }
                        }
                        
        }catch (Exception e) {
                e.printStackTrace();
                Status status = new Status();
                status.code = HttpStatus.SC_INTERNAL_SERVER_ERROR;
                status.message = e.getMessage();
                try {
                        doErrorPage(resp, e);
                } catch (Exception ee) {
                        ee.printStackTrace();
                }
        }
        }
        
        public void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws IOException {

                boolean toXml = true;
                if (req.getParameter("xml")!=null) {
                        toXml = false;  // toJSON
                }

                try {

                        JdoUtils jdoUtils = new JdoUtils();
                        Invoice invoice = (Invoice) getEntity(req, MODEL_PACKAGE);

                        try {
                                jdoUtils.getInvoiceByKey(invoice);

                                // すでに登録されている場合エラー
                                Status status = new Status();
                                status.code = HttpStatus.SC_CONFLICT;
                                status.message = "PK already exists.";
                                doResponse(resp, status, toXml, MODEL_PACKAGE);
                                
                        }catch (JDOObjectNotFoundException e) {

                                // 登録されていなければinsert
//                              invoice = Instance.blancInvoice(invoice);
                                jdoUtils.insert(invoice);
                                doResponse(resp, invoice, toXml, MODEL_PACKAGE, HttpStatus.SC_CREATED);

                        }
                        
                } catch (Exception e) {
                        e.printStackTrace();
                        Status status = new Status();
                        status.code = HttpStatus.SC_INTERNAL_SERVER_ERROR;
                        status.message = e.getMessage();
                        try {
                                doErrorPage(resp, e);
                        } catch (Exception ee) {
                                ee.printStackTrace();
                        }
                }

        }

        public void doPut(HttpServletRequest req, HttpServletResponse resp)
        throws IOException {

                logger.info("put begin.");

                boolean toXml = true;
                if (req.getParameter("xml")!=null) {
                        toXml = false;  // toJSON
                }

                try {
                        Invoice invoice = (Invoice) getEntity(req, MODEL_PACKAGE);

                        logger.info("update step1.");
                        // update
                        JdoUtils jdoUtils = new JdoUtils();
                        jdoUtils.update(invoice);

                        logger.info("update step2.");
                        Status status = new Status();
                        status.code = HttpStatus.SC_OK;
                        status.message = "Updated.";
                        doResponse(resp, status, toXml, MODEL_PACKAGE);

                } 
                // 楽観的ロック失敗
                catch (ConcurrentModificationException e) {
                        logger.info("update step3.");
                        Status status = new Status();
                        status.code = HttpStatus.SC_CONFLICT;
                        status.message = "Optimistic locking failed.";
                        try {
                                doResponse(resp, status, toXml, MODEL_PACKAGE);
                        } catch (Exception ee) {
                                ee.printStackTrace();
                        }
                        
                }
                // 更新エラー
                catch (JDOException e) {
                        logger.info("update step4.");
                        Status status = new Status();
                        status.code = HttpStatus.SC_CONFLICT;
                        status.message = "Updating failed.";
                        try {
                                doResponse(resp, status, toXml, MODEL_PACKAGE);
                        } catch (Exception ee) {
                                ee.printStackTrace();
                        }
                        
                }
                catch (Exception e) {
                        logger.info("update step5.");
                        e.printStackTrace();
                        Status status = new Status();
                        status.code = HttpStatus.SC_INTERNAL_SERVER_ERROR;
                        status.message = e.getMessage();
                        try {
                                doResponse(resp, status, toXml, MODEL_PACKAGE);
                        } catch (Exception ee) {
                                ee.printStackTrace();
                        }
                }

        }

        public void doDelete(HttpServletRequest req, HttpServletResponse resp)
        throws IOException {

                boolean toXml = true;
                if (req.getParameter("xml")!=null) {
                        toXml = false;  // toJSON
                }

                try {

                        // delete completely
                        String completely = req.getParameter("completely");
                        if (completely != null) {
                                JdoUtils jdoUtils = new JdoUtils();

                                String[] idx = null;
                                if (completely.indexOf("-") > 0) {
                                        idx = completely.split("-");
                                } else {
                                        idx = new String[2];
                                        idx[0] = "1";
                                        idx[1] = completely;
                                }

                                for (long i = Long.parseLong(idx[0]); i <= Long.parseLong(idx[1]); i++) {
                                        Invoice rec = new Invoice();
                                        rec.id =i;
                                        jdoUtils.deleteCompletely(rec);
                                }

                                Status status = new Status();
                                status.code = HttpStatus.SC_OK;
                                status.message = completely + " cases were deleted.";
                                doResponse(resp, status, toXml, MODEL_PACKAGE);

                                return;
                        }

                        // パラメータからの値を取得
                        Invoice param = new Invoice();
                        (new RequestMapper()).setValue(req, param);

                        // delete
                        JdoUtils jdoUtils = new JdoUtils();

                        // 削除対象が存在しない場合
                        if (jdoUtils.getInvoiceByKey(param)==null) {
                                Status status = new Status();
                                status.code = HttpStatus.SC_NO_CONTENT;
                                status.message = "No content.";
                                doResponse(resp, status, toXml, MODEL_PACKAGE);
                        }else {
                                jdoUtils.delete(param);
                                Status status = new Status();
                                status.code = HttpStatus.SC_OK;
                                status.message = "Deleted.";
                                doResponse(resp, status, toXml, MODEL_PACKAGE);
                        }
                        
                } catch (Exception e) {
                        e.printStackTrace();
                        Status status = new Status();
                        status.code = HttpStatus.SC_INTERNAL_SERVER_ERROR;
                        status.message = e.getMessage() + "\n";
                        try {
                                doResponse(resp, status, toXml, MODEL_PACKAGE);
                        } catch (Exception ee) {
                                ee.printStackTrace();
                        }
                }
        }
}

JdoUtils

package jp.reflexworks.invoice.util;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.JDOCanRetryException;
import javax.jdo.JDOException;
import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Transaction;

import jp.reflexworks.gae.util.Condition;
import jp.reflexworks.gae.util.EntityConverter;
import jp.reflexworks.gae.util.KeyUtils;
import jp.reflexworks.gae.util.PMF;
import jp.reflexworks.gae.util.QueryUtils;
import jp.reflexworks.invoice.model.Invoice;
import jp.reflexworks.invoice.model.InvoiceBase;
import jp.reflexworks.invoice.model.Order;
import jp.sourceforge.reflex.util.FieldMapper;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;


public class JdoUtils {

        Logger logger = Logger.getLogger(this.getClass().getName());

        // Utils
        KeyUtils keyUtils = new KeyUtils(InvoiceBase.class);
        
        public static final int DEFAULT_PAGESIZE = 100;
        
        
        /*
         * InvoiceをPKであるinvoiceNoで1件検索する
         */
        public Invoice getInvoiceByKey(Invoice param) {

                PersistenceManager pm = PMF.get().getPersistenceManager();
                Invoice invoice = pm.detachCopy(pm.getObjectById(Invoice.class,keyUtils.getChildKey(Invoice.class, param.invoiceNo)));
                pm.close();
                
                return  invoice;
        }


        /*
         * gaeのdatastoreのqueryを使用して検索する
         */
        public Invoice getInvoiceById(Invoice param) {
                DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

                Query query = new Query(Invoice.class.getSimpleName());
                query.addFilter("id", Query.FilterOperator.EQUAL , param.id);
                
                Entity entity = datastore.prepare(query).asSingleEntity();
                EntityConverter entityConverter = new EntityConverter();
                if (entity!=null) {
                        return (Invoice) entityConverter.convert(Invoice.class, entity);
                }
                return null;

        }

        /*
         * invoiceListに明細レコードをつける
         */
        public void complementInvoiceRecord(List<Invoice> invoiceList) {
                
                for(Invoice invoice:invoiceList) {
                        invoice.setOrderList(getOrdersByInvoiceNo(invoice.invoiceNo));
                }
        }

        /*
         * 明細レコードをinvoiceNoで検索する
         */
        public List<Order> getOrdersByInvoiceNo(String invoiceNo) {
                DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

                Query query = new Query(Order.class.getSimpleName());
                query.addFilter("invoiceNo", Query.FilterOperator.EQUAL , invoiceNo);
                
                Iterable<Entity> resultIterable = datastore.prepare(query).asIterable();
                EntityConverter entityConverter = new EntityConverter();
                
                Comparator comparator = new Comparator() {

                        public int compare(Object o1, Object o2) {
                                if (((Order) o1).getSeqno()==null||((Order) o1).getSeqno().equals("")) {
                                        if (((Order) o2).getSeqno()==null||((Order) o2).getSeqno().equals("")) return 0;
                                        else return 1;
                                }else {
                                        if (((Order) o2).getSeqno()==null||((Order) o2).getSeqno().equals("")) return -1;
                                        else {
                                                return ((Order) o1).getSeqno().compareTo(((Order) o2).getSeqno());
                                        }
                                }
                        }

                };
                
                return entityConverter.convert(Order.class, resultIterable,comparator);

        }
/*
         * gaeのdatastoreのqueryを使用して検索する
         */
        public List<Invoice> getEntriesByParam(Invoice param, String pagesize, String nextId) {

/*              
                // ここから検索用条件クラス作成
                class ConditionImpl implements Condition{

                        public String dateString;

                        public ConditionImpl(String dateString) {
                                this.dateString = dateString;
                        }
                        
                        @Override
                        public boolean doCheck(Object param) {
                                
                                // 条件にヒットした場合のみ、resultに追加する
                                if (((Invoice)param).issuedDate!=null&&((Invoice)param).issuedDate.equals(dateString)) {
                                        return true;
                                }

                                return false;
                        }
                };
                //ここまで
                
                ConditionImpl condition = new ConditionImpl("20090725");

                return getEntriesByParam(param, pagesize, nextId, condition);
*/              
                return getEntriesByParam(param, pagesize, nextId, null);
        }
        
        public List<Invoice> getEntriesByParam(Invoice param, String pagesize, String nextId,Condition condition) {
                DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

                Query query = new Query(Invoice.class.getSimpleName());

                // 検索項目(Indexがあるもの) を指定してnewする
                QueryUtils queryUtils = new QueryUtils(new String[]{"invoiceNo","companyName","job","issuedDate"});
                // paramの検索項目がnullでなければaddFiler(FilterOperator.EQUAL)される
                queryUtils.setParam(param, query);

                if (nextId != null) {
                        query.addFilter("id", Query.FilterOperator.GREATER_THAN , Long.parseLong(nextId));
                }
                query.addSort("id");
                int limit = DEFAULT_PAGESIZE;

                if (pagesize != null) {
                        limit = Integer.parseInt(pagesize);
                }

                // Fetch Option
                FetchOptions fetchOptions = FetchOptions.Builder.withLimit(limit);
                Iterable<Entity> resultIterable = datastore.prepare(query).asIterable(fetchOptions);

                EntityConverter entityConverter = new EntityConverter();
                List<Invoice> result = entityConverter.convert(Invoice.class, resultIterable,null,condition);

                return result;
        }

        /**
         * 登録処理(複数件)
         * @param List<Invoice> invoiceList
         * @throws Exception 
         */
        public void insert(List<Invoice> invoiceList)  {
                PersistenceManager pm = PMF.get().getPersistenceManager();
                Transaction tx = pm.currentTransaction();
                int NUM_RETRIES = 3;
                
                try {
                for (int r = 0; r < NUM_RETRIES; r++) {
                        try {
                        tx.begin();
                        logger.info("tx begin.");
                        
                        InvoiceBase invoiceBase;
                        boolean invoiceBaseIsNew = true;
                        try {
                                invoiceBase = (InvoiceBase) pm.getObjectById(InvoiceBase.class,keyUtils.getBaseKeyBuilder().getKey());
                                invoiceBaseIsNew = false;
                                
                        }catch(JDOObjectNotFoundException e){
                                invoiceBase = new InvoiceBase();
                                invoiceBase.setKey(keyUtils.getBaseKeyBuilder().getKey());
                        }

                        for (Invoice invoice:invoiceList) {
                        if (invoice!=null) {
                                invoiceBase.setInvoiceId(invoiceBase.getInvoiceId()+1);
                                invoice.setId(invoiceBase.getInvoiceId());

                                // invoiceはinvoiceNoがPKなのでそれを入れている
                                invoice.setKey(keyUtils.getChildKey(Invoice.class,invoice.invoiceNo));
                                
                                List<Order> orderList = invoice.getOrderList();
                                if (orderList!=null&&orderList.size()>0) {

                                        long orderid = invoiceBase.getOrderId();
                                        for (Order order:orderList) {
                                                orderid++;
                                                order.setId(orderid);

                                                // keyの生成       
                                                KeyFactory.Builder keybuiler = keyUtils.getChildKeyBuilder(Invoice.class,invoice.invoiceNo);
                                                Key key = (keybuiler.addChild(Order.class.getSimpleName(), keyUtils.getRecordKeyName(orderid))).getKey();
                                                order.setKey(key);

                                                order.setInvoiceNo(invoice.invoiceNo);
                                        }
                                        invoiceBase.setOrderId(orderid);

                                }
                                // これでinvoiceがpersistent
                                invoiceBase.addInvoice(invoice);
                        }
                        }
                        
                        if (invoiceBaseIsNew) {
                                pm.makePersistent(invoiceBase);
                        }

                        logger.info("commit.");
                        tx.commit();
                        break;

                } 
                catch (JDOCanRetryException e) {
                        if (r == (NUM_RETRIES - 1)) {
                                throw e;
                        }
                        logger.info("rollbacked.");
                        try {
                                tx.rollback();
                        } catch (Throwable ee) {}
                }
                catch (JDOException e) {
                        logger.info("JDOException:"+e.getMessage());
                        throw e;
                }
                catch (ConcurrentModificationException e) {
                        logger.info("ConcurrentModificationException:"+e.getMessage());
                        throw e;
                }
                } // for loop

                }finally {
                        if (tx.isActive()) {
                                logger.info("rollbacked.");
                                try {
                                        tx.rollback();
                                } catch (Throwable e) {}
                        }
                        logger.info("pm closed.");
                        try {
                                pm.close();
                        } catch (Throwable e) {}
                }
        }

        /**
         * 登録処理(1件)
         * @param invoice invoice
         * @throws Exception 
         */
        public void insert(Invoice invoice)  {
                List<Invoice> invoiceList = new ArrayList<Invoice>();
                invoiceList.add(invoice);
                insert(invoiceList);
        }

        /**
         * 更新処理
         * @param invoice product
         * @throws Exception 
         */
        public void update(Invoice invoice)  {
                PersistenceManager pm = PMF.get().getPersistenceManager();
                FieldMapper fieldMapper = new FieldMapper();
                Transaction tx = pm.currentTransaction();
                int NUM_RETRIES = 3;

                try {
                for (int r = 0; r < NUM_RETRIES; r++) {
                try {
                        tx.begin();
                        logger.info("tx begin.");
                        
                        if (invoice!=null) {
                                
                                try {
                                        
                                        Invoice target = pm.getObjectById(Invoice.class,keyUtils.getChildKey(Invoice.class,invoice.invoiceNo));

                                        // 業務アプリによる楽観的ロック
                                        if (target.getRevision()!=invoice.getRevision()) {
                                                
                                                throw new ConcurrentModificationException();
                                        }else {
                                                
                                                // 更新があるものだけをセット
                                                fieldMapper.setValue(invoice,target);
                                                // 更新カウントアップ
                                                target.setRevison(target.getRevision()+1);
                                                
                                        }
                                        
                                }catch(JDOObjectNotFoundException e){
                                        // 0件更新
                                        throw e;
                                }
                        }
                        

                        logger.info("commit.");
                        tx.commit();
                        break;

                } 
                catch (JDOCanRetryException e) {
                        if (r == (NUM_RETRIES - 1)) {
                                throw e;
                        }
                        logger.info("rollbacked.");
                        try {
                                tx.rollback();
                        } catch (Throwable ee) {}
                }
                catch (JDOException e) {
                        logger.info("JDOException:"+e.getMessage());
                        throw e;
                }
                catch (ConcurrentModificationException e) {
                        logger.info("ConcurrentModificationException:"+e.getMessage());
                        throw e;
                }
                } // for loop
                }finally {
                        if (tx.isActive()) {
                                logger.info("rollbacked.");
                                try {
                                        tx.rollback();
                                } catch (Throwable e) {}
                        }
                        logger.info("pm closed.");
                        try {
                                pm.close();
                        } catch (Throwable e) {}
                }
        }

        /**
         * 削除処理(論理削除)
         * @param invoice invoice
         * @throws Exception 
         */
        public void delete(Invoice invoice)  {
                PersistenceManager pm = PMF.get().getPersistenceManager();
                Transaction tx = pm.currentTransaction();
                int NUM_RETRIES = 3;

                try {
                for (int r = 0; r < NUM_RETRIES; r++) {
                try {
                        tx.begin();
                        logger.info("tx begin.");

                        if (invoice!=null) {
                                try {
                                        Invoice target = pm.getObjectById(Invoice.class,keyUtils.getChildKey(Invoice.class,invoice.invoiceNo));
                                        target.setDeleted(1);   // 1 = deleted
                                }catch(JDOObjectNotFoundException e){
                                        // 0件更新
                                        throw e;
                                }
                        }
                        
                        logger.info("commit.");
                        tx.commit();
                        break;

                } 
                catch (JDOCanRetryException e) {
                        if (r == (NUM_RETRIES - 1)) {
                                throw e;
                        }
                        logger.info("rollbacked.");
                        try {
                                tx.rollback();
                        } catch (Throwable ee) {}
                }
                catch (JDOException e) {
                        logger.info("JDOException:"+e.getMessage());
                        throw e;
                }
                catch (ConcurrentModificationException e) {
                        logger.info("ConcurrentModificationException:"+e.getMessage());
                        throw e;
                }
                } // for loop
                }finally {
                        if (tx.isActive()) {
                                logger.info("rollbacked.");
                                try {
                                        tx.rollback();
                                } catch (Throwable e) {}
                        }
                        logger.info("pm closed.");
                        try {
                                pm.close();
                        } catch (Throwable e) {}
                }
        }

        /**
         * 削除処理(物理削除)
         * @param invoice
         * @throws Exception
         */
        public void deleteCompletely(Invoice invoice)  {

                DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

                try {
                        if (invoice!=null) {
                                datastore.delete(keyUtils.getChildKey(Invoice.class,invoice.invoiceNo));
                        }
                } catch (IllegalArgumentException e) {
                        // 何もしない
                } catch (ConcurrentModificationException e) {
                        // 何もしない
                }
        }

}

Invoice

package jp.reflexworks.invoice.model;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Key;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Invoice {

        @PrimaryKey
        @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
        private Key key;

        @Persistent
        // アプリによる楽観的ロック用
        public long revision;

        public void setRevision(long revision) {
                this.revision = revision;
        }

        @Persistent
        // 1だとtrue(削除済)
        public int deleted;

        @Persistent
        private InvoiceBase invoiceBase; 
        
        @Persistent
        public long id;
        
        public String title;
        
        @Persistent
        public String invoiceNo;
        @Persistent
        public String issuedDate;
        @Persistent
        public String myCompanyName;
        @Persistent
        public String zip;
        @Persistent
        public String address;
        @Persistent
        public String phone;
        @Persistent
        public String companyName;
        public String summary;
        public String invoiceTitle;

        @Persistent
        public String total;
        @Persistent
        public String job;
        
        public String seqno;
        public String description;
        public String quantity;
        public String unitPrice;
        public String lineTotal;
        
        @Persistent(mappedBy = "invoice")  
        public List<Order> orderList;           // 明細レコードは請求レコードの子供
        
        public String subtotal;
        @Persistent
        public String subtotal1;
        
        public String salestax;
        @Persistent
        public String salestax1;                // 請求書レコードでも集計項目を格納する

        public String totalTd;
        @Persistent
        public String totalTd1;                 // 請求書レコードでも集計項目を格納する

        public String note;
        @Persistent
        public String note1;
        public String note2;
        public String note3;

        public Invoice() {
                this.setup();
        }

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

    
    
        public Key getKey() {
                return key;
        }

        public void setKey(Key key) {
                this.key = key;
        }

        private void setup() {
                // 静的表示項目(本来はEntityを別途定義すべき)
                this.title = "御請求書";
                this.myCompanyName = "有限会社バーチャルテクノロジー";
                this.zip = "105-0055";
                this.address = "東京都港区芝1-2-3";
                this.phone = "031232222";
                this.summary = "下記の通り御請求申し上げます。";
                this.invoiceTitle = "御請求金額";
                this.seqno = "No";
                this.description = "品名";
                this.quantity = "数量";
                this.unitPrice = "単価(円)";
                this.lineTotal = "金額(円)";
                this.subtotal = "合計";
                this.salestax = "消費税";
                this.totalTd = "御請求金額";
                this.note = "備考";
                this.note2 = "② お振り込みの際は、下記口座にお願い致します。手数料は貴社にてご負担願います。";
                this.note3 = "③ 振込先:バーチャル銀行芝町支店普通預金2012345〔口座名 有限会社バーチャルテクノロジー〕";

        }
        
        // View -> JDO 
        public void convertTo() {
                // カンマを外す
                this.total = this.total.replaceAll(",","");     
        }

        // JDO -> View 
        public void convertFrom() {
                
                // 明細の合計を計算
                int sub = 0;
                for(Order aRecord:orderList) {
                        if (aRecord.lineTotal!=null&&!aRecord.lineTotal.equals("")) {
                                sub += Integer.parseInt(aRecord.lineTotal);
                                aRecord.convertFrom();
                        }
                }
                // 消費税計算
                int tax = (sub * 5)/100;
                
                // カンマをつける
                DecimalFormat formatter = new DecimalFormat("#,###");            
                this.subtotal1 = formatter.format(sub);
                this.salestax1 = formatter.format(tax);
                this.total = formatter.format(sub + tax);       
                this.totalTd1 = this.total;     
        }
        
        public void addRecord(Order aRecord) {
                if (orderList==null) {
                        setOrderList(new ArrayList<Order>());  
                }
                
                List<Order> recordList = getOrderList();
                orderList.add(aRecord); 
                setOrderList(orderList);
        }

        // Requestオブジェクトから詰めるためのもの
        public void setId(String id) {
                this.id = Long.parseLong(id);
        }

        public String getTitle() {
                return title;
        }
        public void setTitle(String title) {
                this.title = title;
        }
        public String getInvoiceNo() {
                return invoiceNo;
        }
        public void setInvoiceNo(String invoiceNo) {
                this.invoiceNo = invoiceNo;
        }
        public String getIssuedDate() {
                return issuedDate;
        }
        public void setIssuedDate(String issuedDate) {
                this.issuedDate = issuedDate;
        }
        public String getMyCompanyName() {
                return myCompanyName;
        }
        public void setMyCompanyName(String myCompanyName) {
                this.myCompanyName = myCompanyName;
        }
        public String getZip() {
                return zip;
        }
        public void setZip(String zip) {
                this.zip = zip;
        }
        public String getAddress() {
                return address;
        }
        public void setAddress(String address) {
                this.address = address;
        }
        public String getPhone() {
                return phone;
        }
        public void setPhone(String phone) {
                this.phone = phone;
        }
        public String getCompanyName() {
                return companyName;
        }
        public void setCompanyName(String companyName) {
                this.companyName = companyName;
        }
        public String getSummary() {
                return summary;
        }
        public void setSummary(String summary) {
                this.summary = summary;
        }
        public String getInvoiceTitle() {
                return invoiceTitle;
        }
        public void setInvoiceTitle(String invoiceTitle) {
                this.invoiceTitle = invoiceTitle;
        }
        public String getTotal() {
                return total;
        }
        public void setTotal(String total) {
                this.total = total;
        }
        public String getJob() {
                return job;
        }
        public void setJob(String job) {
                this.job = job;
        }
        public String getSeqno() {
                return seqno;
        }
        public void setSeqno(String seqno) {
                this.seqno = seqno;
        }
        public String getDescription() {
                return description;
        }
        public void setDescription(String description) {
                this.description = description;
        }
        public String getQuantity() {
                return quantity;
        }
        public void setQuantity(String quantity) {
                this.quantity = quantity;
        }
        public String getUnitPrice() {
                return unitPrice;
        }
        public void setUnitPrice(String unitPrice) {
                this.unitPrice = unitPrice;
        }
        public String getLineTotal() {
                return lineTotal;
        }
        public void setLineTotal(String lineTotal) {
                this.lineTotal = lineTotal;
        }
        public List<Order> getOrderList() {
                return orderList;
        }
        public void setOrderList(List<Order> record) {
                this.orderList = record;
        }
        public String getSubtotal() {
                return subtotal;
        }
        public void setSubtotal(String subtotal) {
                this.subtotal = subtotal;
        }
        public String getSubtotal1() {
                return subtotal1;
        }
        public void setSubtotal1(String subtotal1) {
                this.subtotal1 = subtotal1;
        }
        public String getSalestax() {
                return salestax;
        }
        public void setSalestax(String salestax) {
                this.salestax = salestax;
        }
        public String getSalestax1() {
                return salestax1;
        }
        public void setSalestax1(String salestax1) {
                this.salestax1 = salestax1;
        }
        public String getTotalTd() {
                return totalTd;
        }
        public void setTotalTd(String totalTd) {
                this.totalTd = totalTd;
        }
        public String getTotalTd1() {
                return totalTd1;
        }
        public void setTotalTd1(String totalTd1) {
                this.totalTd1 = totalTd1;
        }
        public String getNote() {
                return note;
        }
        public void setNote(String note) {
                this.note = note;
        }
        public String getNote1() {
                return note1;
        }
        public void setNote1(String note1) {
                this.note1 = note1;
        }
        public String getNote2() {
                return note2;
        }
        public void setNote2(String note2) {
                this.note2 = note2;
        }
        public String getNote3() {
                return note3;
        }
        public void setNote3(String note3) {
                this.note3 = note3;
        }


        public long getId() {
                return id;
        }

        public int getDeleted() {
                return deleted;
        }

        public void setDeleted(int deleted) {
                this.deleted = deleted;
        }

        public long getRevision() {
                return revision;
        }

        public void setRevison(long revision) {
                this.revision = revision;
        }
        
        // EntityConvertoer用
        public void setDeleted(Long deleted) {
                this.deleted = (int)((long) deleted);
        }
        
        
}

Order

package jp.reflexworks.invoice.model;

import java.text.DecimalFormat;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Key;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Order {

        @PrimaryKey
        @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
        private Key key;

        @Persistent
        private Invoice invoice;
        @Persistent
        private long id;
        @Persistent
        private String invoiceNo;               // 受注レコードとの関連をひもづける
        @Persistent
        public String seqno;
        @Persistent
        public String productcd;
        @Persistent
        public String description;
        @Persistent
        public String quantity;
        @Persistent
        public String unitPrice;
        @Persistent
        public String lineTotal;

        // 1だとtrue(削除済)
        public int deleted;

        // View -> JDO 
        public void convertTo() {
                // カンマを外す
                this.unitPrice = this.unitPrice.replaceAll(",","");     
                this.lineTotal = this.lineTotal.replaceAll(",","");     
        }

        // JDO -> View 
        public void convertFrom() {
                // カンマをつける
                DecimalFormat formatter = new DecimalFormat("#,###");            
                this.unitPrice = formatter.format(Integer.parseInt(this.unitPrice));    
                this.lineTotal = formatter.format(Integer.parseInt(this.lineTotal));    
        }

        public String getInvoiceNo() {
                return invoiceNo;
        }
        public void setInvoiceNo(String invoiceNo) {
                this.invoiceNo = invoiceNo;
        }
        public String getSeqno() {
                return seqno;
        }
        public void setSeqno(String seqno) {
                this.seqno = seqno;
        }
        public String getDescription() {
                return description;
        }
        public void setDescription(String description) {
                this.description = description;
        }
        public String getQuantity() {
                return quantity;
        }
        public void setQuantity(String quantity) {
                this.quantity = quantity;
        }
        public String getUnitPrice() {
                return unitPrice;
        }
        public void setUnitPrice(String unitPrice) {
                this.unitPrice = unitPrice;
        }
        public String getLineTotal() {
                return lineTotal;
        }
        public void setLineTotal(String lineTotal) {
                this.lineTotal = lineTotal;
        }

        public Key getKey() {
                return key;
        }

        public void setKey(Key key) {
                this.key = key;
        }

        public Invoice getInvoice() {
                return invoice;
        }

        public void setInvoice(Invoice invoice) {
                this.invoice = invoice;
        }

        public long getId() {
                return id;
        }

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

        public int getDeleted() {
                return deleted;
        }

        public void setDeleted(int deleted) {
                this.deleted = deleted;
        }

        public String getProductcd() {
                return productcd;
        }

        public void setProductcd(String productcd) {
                this.productcd = productcd;
        }
        
        
        
}