001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ui.components.extensions;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.gwt.CmsPrefetchSerializationPolicy;
034import org.opencms.gwt.shared.property.CmsPropertiesBean;
035import org.opencms.gwt.shared.property.CmsPropertyChangeSet;
036import org.opencms.gwt.shared.rpc.I_CmsVfsService;
037import org.opencms.main.CmsException;
038import org.opencms.main.CmsLog;
039import org.opencms.ui.A_CmsUI;
040import org.opencms.ui.I_CmsUpdateListener;
041import org.opencms.ui.actions.CmsPropertiesDialogAction;
042import org.opencms.ui.shared.rpc.I_CmsPropertyClientRpc;
043import org.opencms.ui.shared.rpc.I_CmsPropertyServerRpc;
044import org.opencms.ui.util.CmsNewResourceBuilder;
045import org.opencms.util.CmsUUID;
046
047import java.util.HashSet;
048import java.util.List;
049
050import org.apache.commons.logging.Log;
051
052import com.google.common.collect.Lists;
053import com.google.common.collect.Sets;
054import com.google.gwt.user.server.rpc.RPC;
055import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
056import com.vaadin.server.AbstractExtension;
057import com.vaadin.ui.UI;
058
059/**
060 * Extension used for the GWT-based property dialog called from the workplace.
061 *
062 * This keeps track of the list of resources which were visible when the property dialog was opened, allowing
063 * the user to navigate through the list with prev/next buttons.
064 */
065public class CmsPropertyDialogExtension extends AbstractExtension implements I_CmsPropertyServerRpc {
066
067    /** Logger instance for this class. */
068    private static final Log LOG = CmsLog.getLog(CmsPropertyDialogExtension.class);
069
070    /** Serial version id. */
071    private static final long serialVersionUID = 1L;
072
073    /** The list of structure ids. */
074    List<CmsUUID> m_ids = Lists.newArrayList();
075
076    /** Current position in the ID list. */
077    int m_position;
078
079    /** Helper used to create a new resource after entering its properties. */
080    private CmsNewResourceBuilder m_newResourceBuilder;
081
082    /** The structure ids of possibly updated resources. */
083    private HashSet<CmsUUID> m_updatedIds = Sets.newHashSet();
084
085    /** The update listener. */
086    private I_CmsUpdateListener<String> m_updateListener;
087
088    /**
089     * Creates a new instance and binds it to a UI instance.<p>
090     *
091     * @param ui the UI to bind this extension to
092     * @param updateListener the update listener
093     */
094    public CmsPropertyDialogExtension(UI ui, I_CmsUpdateListener<String> updateListener) {
095        extend(ui);
096        m_updateListener = updateListener;
097        registerRpc(this, I_CmsPropertyServerRpc.class);
098    }
099
100    /**
101     * Open property editor for the resource with the given structure id.<p>
102     *
103     * @param structureId the structure id of a resource
104     * @param allIds structure ids of resources for the prev/next navigation
105     * @param editName controls whether the file name should be editable
106     */
107    public void editProperties(CmsUUID structureId, List<CmsUUID> allIds, boolean editName) {
108
109        m_position = allIds.indexOf(structureId);
110
111        m_ids = allIds;
112        m_updatedIds.add(structureId);
113        boolean online = A_CmsUI.getCmsObject().getRequestContext().getCurrentProject().isOnlineProject();
114        getRpcProxy(I_CmsPropertyClientRpc.class).editProperties(
115            "" + structureId,
116            editName,
117            online || (allIds.size() < 2));
118    }
119
120    /**
121     * Opens the property dialog for a resource to be created with the 'New' dialog.<p>
122     *
123     * @param builder the resource builder used by the 'New' dialog to create the resource
124     */
125    public void editPropertiesForNewResource(CmsNewResourceBuilder builder) {
126
127        try {
128            CmsPropertiesBean propData = builder.getPropertyData();
129            String serializedPropData = RPC.encodeResponseForSuccess(
130                I_CmsVfsService.class.getMethod("loadPropertyData", CmsUUID.class),
131                propData,
132                CmsPrefetchSerializationPolicy.instance());
133            getRpcProxy(I_CmsPropertyClientRpc.class).editPropertiesForNewResource(serializedPropData);
134            m_newResourceBuilder = builder;
135        } catch (Exception e) {
136            throw new RuntimeException(e);
137        }
138    }
139
140    /**
141     * @see org.opencms.ui.shared.rpc.I_CmsPropertyServerRpc#onClose(long)
142     */
143    public void onClose(long delayMillis) {
144
145        remove();
146        if (delayMillis > 0) {
147            try {
148                Thread.sleep(delayMillis);
149            } catch (InterruptedException e) {
150                // ignore
151            }
152        }
153        List<String> updates = Lists.newArrayList();
154        for (CmsUUID id : m_updatedIds) {
155            updates.add("" + id);
156        }
157        m_updateListener.onUpdate(updates);
158    }
159
160    /**
161     * @see org.opencms.ui.shared.rpc.I_CmsPropertyServerRpc#removeExtension()
162     */
163    public void removeExtension() {
164
165        remove();
166    }
167
168    /**
169     * @see org.opencms.ui.shared.rpc.I_CmsPropertyServerRpc#requestNextFile(int)
170     */
171    public void requestNextFile(int offset) {
172
173        int newPos = m_position;
174        int count = 0;
175        do {
176            newPos = nextIndex(newPos, offset);
177            count += 1;
178            if (count > m_ids.size()) {
179                // prevent infinite loop in case user suddenly can't edit any properties anymore (e.g. through a permission change)
180                newPos = m_position;
181                break;
182            }
183        } while (!canEdit(m_ids.get(newPos)));
184        m_position = newPos;
185        CmsUUID nextId = m_ids.get(m_position);
186        m_updatedIds.add(nextId);
187        getRpcProxy(I_CmsPropertyClientRpc.class).sendNextId("" + nextId);
188    }
189
190    /**
191     * @see org.opencms.ui.shared.rpc.I_CmsPropertyServerRpc#savePropertiesForNewResource(java.lang.String)
192     */
193    public void savePropertiesForNewResource(String data) {
194
195        try {
196            getRpcProxy(I_CmsPropertyClientRpc.class).confirmSaveForNew();
197            ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(
198                Thread.currentThread().getContextClassLoader(),
199                null);
200            // Filling stream reader with data
201            streamReader.prepareToRead(data);
202            // Reading deserialized object from the stream
203            CmsPropertyChangeSet changes = (CmsPropertyChangeSet)(streamReader.readObject());
204            m_newResourceBuilder.setPropertyChanges(changes);
205            m_newResourceBuilder.safeCreateResource();
206            remove();
207        } catch (Exception e) {
208            throw new RuntimeException(e);
209        }
210    }
211
212    /**
213     * Checks if the user can edit the resource with the given id.<p>
214     *
215     * @param id a structure id
216     * @return true if the user can edit the file
217     */
218    protected boolean canEdit(CmsUUID id) {
219
220        CmsObject cms = A_CmsUI.getCmsObject();
221        CmsResource res = null;
222        try {
223            res = cms.readResource(id, CmsResourceFilter.ALL);
224            boolean result = CmsPropertiesDialogAction.VISIBILITY.getVisibility(
225                A_CmsUI.getCmsObject(),
226                Lists.newArrayList(res)).isActive();
227            return result;
228        } catch (CmsException e) {
229            LOG.error(e.getLocalizedMessage(), e);
230            return false;
231        }
232    }
233
234    /**
235     * Computes the next index.<p>
236     *
237     * This method by called more than once per press of next/prev, since uneditable resources need to be skipped.
238     *
239     * @param pos the current position
240     * @param offset the offset (+1 or -1)
241     * @return the next index
242     */
243    int nextIndex(int pos, int offset) {
244
245        return (pos + offset + m_ids.size()) % m_ids.size();
246    }
247
248}